diff --git a/docs/KNOWN_LIMITATIONS.md b/docs/KNOWN_LIMITATIONS.md index b80a8c7..25dcd4e 100644 --- a/docs/KNOWN_LIMITATIONS.md +++ b/docs/KNOWN_LIMITATIONS.md @@ -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 diff --git a/docs/README.md b/docs/README.md index b7c96c5..c9e3f97 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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, diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 4ebf2e6..ba9da04 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -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 diff --git a/docs/design/STEP20C_EGL_FX_TASK_LIMIT_PROTOCOL.md b/docs/design/STEP20C_EGL_FX_TASK_LIMIT_PROTOCOL.md new file mode 100644 index 0000000..949858e --- /dev/null +++ b/docs/design/STEP20C_EGL_FX_TASK_LIMIT_PROTOCOL.md @@ -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. diff --git a/docs/design/STEP20C_HEADLESS_ALLOCATOR_TRIAGE.md b/docs/design/STEP20C_HEADLESS_ALLOCATOR_TRIAGE.md index e0211df..9f050e2 100644 --- a/docs/design/STEP20C_HEADLESS_ALLOCATOR_TRIAGE.md +++ b/docs/design/STEP20C_HEADLESS_ALLOCATOR_TRIAGE.md @@ -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. diff --git a/docs/design/STEP20C_HEADLESS_EGL_FX_PROBE.md b/docs/design/STEP20C_HEADLESS_EGL_FX_PROBE.md index 6ff9c89..5afe8c3 100644 --- a/docs/design/STEP20C_HEADLESS_EGL_FX_PROBE.md +++ b/docs/design/STEP20C_HEADLESS_EGL_FX_PROBE.md @@ -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. diff --git a/tooling/probes/step20c-egl-fx/README.md b/tooling/probes/step20c-egl-fx/README.md index f908842..d3ae19a 100644 --- a/tooling/probes/step20c-egl-fx/README.md +++ b/tooling/probes/step20c-egl-fx/README.md @@ -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