docs(mosaic): record EGL namespace failure and design probe task quota

This commit is contained in:
fy59 2026-10-17 07:20:24 +02:00
parent 99f1fbeb40
commit 388bd15f35
7 changed files with 246 additions and 8 deletions

View file

@ -1,5 +1,12 @@
# Known limitations
The [EGL/FX attempt](design/STEP20C_HEADLESS_EGL_FX_PROBE.md#observed-single-attempt--2026-10-17)
failed at Bubblewrap namespace creation before the helper: EGL_NOT_RUN /
ALLOCATOR_NOT_ATTEMPTED; budget consumed. A probe-local cgroup task-limit
[protocol is DESIGN_ONLY](design/STEP20C_EGL_FX_TASK_LIMIT_PROTOCOL.md),
EGL_FX_RETRY_PREP_BLOCKED; new runtime NOT_RUN. No production contract or
STEP20C status changes, and no new native execution is authorized.
The [CORE-C V2 follow-up decision V4](design/STEP20C_COREC_V2_INTEGRATION_DECISION_V4.md)
records five new consumed reservations and 32 output captures, separately from
the historical 12 runs/102 captures. Migration/refocus and root/nested OOM are

View file

@ -1,9 +1,12 @@
# Documentation
[Isolated EGL/FX probe preparation](design/STEP20C_HEADLESS_EGL_FX_PROBE.md)
is EXPERIMENTAL / EGL_FX_PROBE_READY_FOR_APPROVAL; its source-only fixture and
reported offline tests await independent validation. Runtime NOT_RUN; separate
conversational approval is required for the single native measurement.
[Isolated EGL/FX probe](design/STEP20C_HEADLESS_EGL_FX_PROBE.md)
records the single 2026-10-17 namespace failure: EXPERIMENTAL /
EGL_FX_PROBE_BLOCKED_BEFORE_HELPER / EGL_NOT_RUN / ALLOCATOR_NOT_ATTEMPTED.
The historical READY_FOR_APPROVAL verdict preceded that consumed attempt.
The [task-limit protocol design](design/STEP20C_EGL_FX_TASK_LIMIT_PROTOCOL.md)
selects probe-local cgroup admission for future implementation;
EGL_FX_RETRY_PREP_BLOCKED, new runtime NOT_RUN. No retry is authorized.
[Gate 2 headless boot](design/STEP20C_HEADLESS_BOOT_GATE2.md) records the single
V5 native failure: EXPERIMENTAL / PRISTINE_ALLOCATOR_BLOCKED / CORE_NOT_RUN,

View file

@ -1,5 +1,12 @@
# Roadmap
The [EGL/FX attempt](design/STEP20C_HEADLESS_EGL_FX_PROBE.md#observed-single-attempt--2026-10-17)
failed at Bubblewrap namespace creation before the helper: EGL_NOT_RUN /
ALLOCATOR_NOT_ATTEMPTED; budget consumed. A probe-local cgroup task-limit
[protocol is DESIGN_ONLY](design/STEP20C_EGL_FX_TASK_LIMIT_PROTOCOL.md),
EGL_FX_RETRY_PREP_BLOCKED; new runtime NOT_RUN. No production contract or
STEP20C status changes, and no new native execution is authorized.
The [CORE-C V2 follow-up V4](design/STEP20C_COREC_V2_INTEGRATION_DECISION_V4.md)
is PARTIALLY_ADVANCED: five consumed runs/32 output captures, sampled migration/
refocus and root/nested OOM progress, fullscreen comparison and native G3

View file

@ -0,0 +1,156 @@
# STEP20C — EGL/FX task-limit protocol design — 2026-10-17
Status: **EXPERIMENTAL / DESIGN_ONLY / EGL_FX_RETRY_PREP_BLOCKED**.
New runtime: **NOT_RUN**. No scope, Bubblewrap, EGL/FX helper or compositor is
launched by this mission. This documentary batch deliberately leaves the
historical fixture source unchanged. It does not implement cgroup admission.
The [observed failure](STEP20C_HEADLESS_EGL_FX_PROBE.md#observed-single-attempt--2026-10-17)
precedes this design; its consumed reservation remains immutable.
## Evidence, guarantees and admission
| Route / evidence | Risk | Bound actually supplied | Admission condition / disposition |
| --- | --- | --- | --- |
| Historical RLIMIT_NPROC=256 before bwrap; incident reports 1126 UID threads | Immediate task creation failure, also other possible EAGAIN causes | Real-UID thread accounting, not a probe-local quota | REJECT for retry; no syscall-level causal proof |
| Raise NPROC from a procfs count | Count is racy; unrelated UID load changes; no descendant aggregate | Still only real-UID accounting | REJECT as replacement for task containment; no arbitrary count-plus-margin |
| Omit explicit NPROC override, retain inherited soft/hard limits | Removes the intended extra UID restriction; inherited limit can still cause refusal | No new local task bound by itself | Only with independently verified cgroup admission; never silently raise inherited limits |
| Fresh delegated cgroup v2 with pids.max, possibly manager-owned transient user scope | Wrong subtree, post-spawn migration race, insufficient ancestor budget, unauthorized cleanup | Hierarchical task creation limit for admitted descendants, counting threads | SELECTED design direction, NOT IMPLEMENTED or locally qualified; all gates below required |
| Prior benign-child scope experiments in Gate 2 | Different launcher context, identity and lifecycle | Evidence for that earlier scope only | Do not inherit PASS; repeat only under new explicit approval |
| AS 1 GiB / CPU 5 s / NOFILE 64 / FSIZE 1 MiB / CORE 0 | Multiple processes multiply several limits; tmpfs and total storage remain separate | Per-process address space/CPU/FD limits, per-file size, disabled core dumps | Preserve exact settings; no claim of aggregate memory/CPU/storage quota |
| 8 s payload + 2 s cleanup; 1 MiB per raw pipe | Stuck cleanup, leaked descendants, parent death | Supervised deadline and captured-output bounds on observed paths | Preserve; failure to prove cleanup is BLOCKED, never a retry |
| Closed mount/environment/device/FD policy | Dlopen drift, inherited socket or control-plane leak | Fixed exposure and sealed inputs, not total resource containment | Preserve pins, ABI, manifest closure and exact allowlists; no new host device/mount |
Linux [RLIMIT_NPROC documentation](https://man7.org/linux/man-pages/man2/getrlimit.2.html)
explains the real-UID thread limit and EAGAIN at creation. A procfs scan is a
refusal diagnostic, not an atomic reservation: disappearing entries, unreadable
status, UID changes or inconsistent thread counts must be reported explicitly.
The [kernel cgroup v2 contract](https://docs.kernel.org/admin-guide/cgroup-v2.html#pid)
counts TIDs hierarchically and rejects task creation over the limit. Migration
can exceed pids.max, so a verified empty private group and controlled admission
are essential. A namespace alone supplies no task quota.
## Minimal selected protocol, pending implementation and review
Use one fresh, exclusively owned **domain** cgroup for the probe descendants;
keep the collecting supervisor outside the kill target. Prefer the existing
user-manager/delegation approach over a new resource framework. Follow
[systemd's delegation ownership rules](https://systemd.io/CGROUP_DELEGATION/):
do not mkdir or write arbitrary manager-owned subtrees. A transient scope is
only a candidate integration mechanism, not proof that its tasks were admitted
before execution. No systemd-run or control-plane mutation is allowed now.
A future source-only patch is conditional on independent design acceptance.
It must implement the following invariants together, not merely delete NPROC:
1. Validate cgroup2 mount identity from mountinfo, domain type, delegation,
pids controller availability/enabling, exact owned path, directory identity,
permissions, ancestor constraints and current launcher membership. Reject
session-scope placement where the selected delegation requires app.slice.
Read-only observations in this mission show this agent in a session scope;
root controller availability does not prove delegation. No permission or
membership repair is automatic. Historical scope names/PIDs grant no authority.
2. Seal a finite task budget in the new manifest. Candidate **pids.max=256**
preserves the old intended local ceiling, now counting bwrap, wrappers and
all helper threads together. It is not an assertion of sufficient capacity:
LP_NUM_THREADS=2 does not bound every Mesa thread. Review the task inventory
and ancestor headroom; insufficient capacity refuses without raising limits.
3. Establish ownership and a blocked-child admission handshake before any
prlimit/bwrap/helper exec or descendant creation. Verify the child's PID
lifetime, actual membership and effective pids.max before release. Moving
an already running payload afterwards is forbidden. Parent failure/EOF must
leave the child unable to execute the payload. Never migrate the desktop or
collecting agent. No cgroup mount, directory FD or user bus reaches payload.
4. After cgroup admission, omit only the explicit NPROC=256 override, retain
the inherited UID soft/hard limits and all other prlimit settings. Record
inherited NPROC, real UID, diagnostic thread-count provenance and effective
cgroup properties; any known lower-than-UID-load NPROC refuses explicitly.
Count uncertainty/drift cannot be accepted as a proof of headroom. No fallback
to a larger NPROC, unlimited task count, unconfined launch or new device.
5. Reserve the new single attempt durably with O_EXCL before creating its
scope/child or otherwise mutating native execution state. A collision,
partial reservation, admission failure or spawn failure consumes that attempt;
do not remove ATTEMPT. Passive inspection writes nothing. Journal bounded
admission, release, result and cleanup phases, including interrupted paths.
6. Prove cleanup of only the owned scope/subtree, collect the owned child and
observe subtree empty before removal. Require a tested external cleanup
authority on supervisor death; die-with-parent alone is not that proof.
Any use of cgroup.kill requires verified exclusive ownership and supervisor
exclusion. PID lists alone cannot prove subtree emptiness; use recursive
cgroup.events populated=0 and task counters as well as child collection.
Namespace teardown remains an additional mechanism. Cleanup exceeding the
fixed reserve is failure, never permission for broad kills or retry.
pids.max supplies no aggregate memory, CPU-time or disk ceiling. Preserve the
existing limited claims; do not present AS times task count as a usable memory
reservation. Ancestor pressure and same-UID hostile interference remain risks.
Adding memory/CPU controllers would require a separately reviewed protocol,
not an implicit expansion here. No global UID or manager configuration changes.
## Offline acceptance matrix — required before new preparation
These are **PLANNED / NOT IMPLEMENTED / NOT TESTED for the new protocol**.
The existing fixture's tests cover only its historical protocol. New tests
must call the actual new admission/refusal paths using fake procfs, cgroupfs,
manager and process adapters; no native scope, bwrap, helper or device call.
An execution sentinel must fail the test on any unintended native boundary.
| Offline case | Required observation |
| --- | --- |
| NPROC below UID threads; 256 vs 1126 fixture | Explicit refusal before native state or reservation |
| UID/count drift, disappearing/unreadable proc entries | Diagnostic uncertainty/refusal, no invented capacity |
| Missing controller, cgroup v1, threaded/foreign/replaced group, symlink | Refusal, no writes to foreign subtree |
| Wrong mount identity, unexpected mount/device or environment injection | Refusal before release; inherited bus/FD absent from payload |
| Missing delegation/ownership, wrong launcher scope, exhausted ancestor | Refusal; no fallback and no desktop migration |
| Wrong child lifetime/membership, wrong or unlimited pids.max | Blocked child never released |
| Reservation collision, hash mismatch or altered manifest | No launch; prior reservation/bytes preserved |
| Timeout, closed/full pipes, admission failure, parent death | Bounded receipts; only owned cleanup; unproven collection fails |
| Cleanup target replaced or supervisor inside target | Refuse kill; preserve failure evidence |
| Happy passive path | No mutation, no reservation, no native spawn |
Review exact source hashes and revised manifest semantics on the same bytes;
AST/format checks and targeted tests precede a second review of implementation.
Do not inherit any runtime PASS from mocked tests, linked ELF or the earlier
755 dependency identities. No C, SceneFX, wlroots, engine, package, production
patch or public ABI change is needed or authorized by this documentary design.
## Future authorization sequence — not executable instructions
First complete the conditional source-only implementation and offline review.
Then request a separate, bounded **benign-child cgroup qualification** approval:
new private 0700 LAB, exact sealed argv/environment, finite task-limit test,
admission-before-exec and timeout/parent-death cleanup evidence. Historical
scope experiments do not waive this gate. No EGL or Bubblewrap in that phase.
Only after that qualification passes, prepare a distinct fresh 0700 EGL/FX LAB
and revised manifest with the new launcher, effective resource policy,
identities, exact command, ownership and cleanup protocol. Preserve pinned
sources/library ABI and the existing closed device/mount/FD policy. Obtain
independent review on that exact manifest and request **new conversational
approval** for one EGL/FX attempt with its own budget. Do not spend it during
preparation, inherit the old approval, or silently retry any failure.
There is deliberately **no exact future native command** in this edition:
no implemented/qualified cgroup launcher or new reviewed manifest exists.
**EGL_FX_RETRY_PREP_BLOCKED** identifies those preparation gates, not a new EGL
failure. It does not require a human decision to weaken containment. The next
smallest technical step is reviewed source-only admission implementation;
any later native qualification still needs explicit user authorization.
## Validation of this documentary batch — 2026-10-17
The existing `test_probe.py` suite passes **23/23** with
`PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tooling/probes/step20c-egl-fx -p test_probe.py -v`.
It uses inert Python children for supervisor paths and mocks the native launch
boundary; no Bubblewrap or real helper is run. AST parsing of the three existing
Python scripts and file-target checks for local links in the seven changed
Markdown files pass. `git diff --check` passes. These checks do not test the
proposed cgroup protocol or certify Python style changes (there are none).
Read-only before/after SHA256 inventories agree for all 593 regular non-symlink
files under the historical LAB; its manifest still lists 755 dependency
identities, including external files. This inventory checks retained file bytes,
not an atomic filesystem snapshot or metadata preservation guarantee. No raw
inventory, log, private manifest, binary or personal scope identifier is published.
Native build, C harness, cgroup qualification, Bubblewrap and EGL/FX: **NOT RUN**
in this mission. Independent review and actual publication outcome are reported
in the task handoff; this document does not predeclare their PASS.

View file

@ -89,3 +89,15 @@ This helper is a proposal, NOT_RUN: measuring EGL identity/caps/formats cannot
resolve an absent allocator and would not qualify boot, rendering or CORE. If it finds no compatible allocation under the existing containment,
retain PRISTINE_ALLOCATOR_BLOCKED / CORE_NOT_RUN and report the limitation;
any renderer or sandbox design change needs its own scoped decision.
## EGL/FX follow-up — 2026-10-17
The [single EGL/FX attempt](STEP20C_HEADLESS_EGL_FX_PROBE.md#observed-single-attempt--2026-10-17)
consumed its reservation but failed at Bubblewrap namespace creation before the
helper. EGL_NOT_RUN / ALLOCATOR_NOT_ATTEMPTED: it supplies no new allocator or
renderer evidence and leaves V5 PRISTINE_ALLOCATOR_BLOCKED / CORE_NOT_RUN intact.
The [task-limit design](STEP20C_EGL_FX_TASK_LIMIT_PROTOCOL.md) is offline only;
its proposed cgroup/scope is a new protocol, not authorized by the older
scope-free proposal above. New qualification and EGL execution need separate
approvals. Historical LABs and raw verdicts remain immutable.

View file

@ -1,6 +1,9 @@
# STEP20C — isolated EGL/FX probe preparation
Status: **EXPERIMENTAL / EGL_FX_PROBE_READY_FOR_APPROVAL / runtime NOT_RUN**.
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.
Retry preparation: **EGL_FX_RETRY_PREP_BLOCKED / runtime NOT_RUN**.
See the [task-limit protocol](STEP20C_EGL_FX_TASK_LIMIT_PROTOCOL.md).
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](STEP20C_HEADLESS_BOOT_GATE2.md) remains
@ -80,7 +83,7 @@ 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.
## Fixed confinement and unconsumed budget
## 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,
@ -105,7 +108,7 @@ 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.
## Preparation and approval boundary
## 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.
@ -123,7 +126,8 @@ 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. A future command has this shape:
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:
```sh
/usr/bin/env -i PATH=/usr/bin LC_ALL=C /usr/bin/python3 -I \
@ -138,3 +142,43 @@ 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 repository run.py 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](https://man7.org/linux/man-pages/man2/getrlimit.2.html).
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 follow-up. The [new protocol design](STEP20C_EGL_FX_TASK_LIMIT_PROTOCOL.md)
requires separately reviewed implementation and new conversational approval.

View file

@ -1,5 +1,14 @@
# STEP20C — fixture source-only EGL/FX
**Mise à jour du 17/10/2026 : essai unique consommé, BLOCKED_BEFORE_HELPER.**
Bubblewrap a échoué à créer le namespace ; EGL_NOT_RUN /
ALLOCATOR_NOT_ATTEMPTED. Les instructions ci-dessous décrivent la préparation
historique et ne constituent plus une procédure de relance admissible.
Ne relancer aucun ancien LAB. Le [nouveau protocole de limite de tâches](../../../docs/design/STEP20C_EGL_FX_TASK_LIMIT_PROTOCOL.md)
est DESIGN_ONLY / EGL_FX_RETRY_PREP_BLOCKED : admission cgroup à implémenter,
tester hors ligne et qualifier séparément avant toute nouvelle autorisation EGL.
Le code historique reste inchangé ; ses tests ne valident pas ce nouveau protocole.
**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