24 KiB
STEP20C — EGL/FX task-limit protocol design — 2026-10-17
Status: EXPERIMENTAL / IMPLEMENTED_OFFLINE. Historical offline review is accepted; independent review of the current global-headroom delta is accepted. The final audit of this delta is PASS / ACCEPTED, with no blocking finding. New runtime: NOT_RUN. No scope, Bubblewrap, EGL/FX helper or compositor is launched by this mission. The source-only runner now implements the gate; qualification against the real manager remains separately approval-gated. The observed failure precedes this design; its consumed reservation remains immutable.
Adopted implementation slice — 2026-10-17
The implementation ticket authorizes source-only Python and inert tests now. Use an explicitly owned transient user scope under app.slice, Delegate=yes, TasksMax=256, with supervisor outside the scope and bounded manager runtime and stop deadlines. A fixed bootstrap must wait before any payload exec; validate the manager/kernel/process identities and release once only. Channel EOF, invalid nonce, missing proof or timeout must fail closed. Keep NPROC inherited, removing only the explicit 256 override after cgroup admission. No real scope or payload may run in this implementation mission. Code and inert behavioral validation establish IMPLEMENTED_OFFLINE; scoped code review is accepted; final audit and its one bounded repair are complete. The private observer repair has independent targeted acceptance. Fresh private qualification LABs/manifests and exact benign commands are required; benign qualification and EGL execution each need separate conversational approval. The historical validation section below retains its original provenance and does not describe current implementation coverage.
First benign preflight refusal and real-UID accounting — 2026-10-17
The single nominal invocation refused with UID process divergent inside
ProcFS.uid_threads(), before ATTEMPT, scope creation or payload execution.
The two historical benign LABs remain unconsumed and must not be reused.
Native cgroup qualification has not passed; parent-death remains NOT_RUN.
Resource accounting must include every thread belonging to the requested real
UID, including legitimate mixed credentials such as (1000, 0, 0, 0) used by
a privileged portal helper. This diagnostic is not a graphical-service admission
scan. Sample PID/startticks, proc directory identity, all four UIDs, cgroup,
Threads and task entries before/after; unreadability, disappearance or observed
drift refuses rather than undercounting. The snapshot is not an atomic reservation.
Authentication of the supervisor, manager and bootstrap remains separate:
ProcFS.identity() still requires homogeneous real/effective/saved/filesystem
UIDs matching proc ownership, with strict lifetime and cgroup checks. The count
fix must not relax that predicate or introduce process-name/privilege exceptions.
Only source correction, inert regression tests, read-only diagnostics and fresh
LAB preparation are authorized here; any real benign execution requires fresh
conversational approval.
UID count correction validation — 2026-10-17
The actual counter now uses two observations and compares the PID set, process
lifetime, proc inode/device, all four UIDs, cgroup, Threads and exact task names.
Foreign real UIDs contribute zero; their credentials and directory identity are
compared across observations. No whitelist, privilege bypass or partial total
is accepted. The strict identity() source is unchanged.
The offline suite passes 86 tests (75 existing plus 11 new regressions). The unchanged private parent-death observer passes its 14 deterministic tests. Two fresh 0700 LABs have distinct nonces and sealed manifests; both isolated passive CLI plans pass via Desktop Commander on ArchASP under app.slice, without approval flags, ATTEMPT or payload execution.
One live counter diagnostic refused on a precisely identified disappearing proc entry (ENOENT); it sampled the legitimate mixed-UID helper and its one thread without the historical homogeneous-UID refusal. A separate read-only preflight refused a changed process set. Neither was repeated to obtain PASS. These are expected fail-closed outcomes, not a measured complete UID total or a successful real preflight. Snapshots cannot detect every transient change or reserve task capacity. The next approved invocation can still refuse on drift. Native admission and parent-death cleanup remain NOT_RUN / NOT QUALIFIED; no cgroup runtime PASS, Mesa capacity or STEP20C readiness follows.
Bounded independent delta review is ACCEPTED; the single final audit is PASS with no blocking finding and independently reproduces 86/86 plus 14/14 tests, manifest/fixture hashes and historical preservation. Disposition: CGROUP_UID_COUNT_FIX_READY_FOR_BENIGN_APPROVAL, source-only. Publication of the correction does not authorize the three future native qualification commands.
Conservative global headroom fast path — 2026-10-17
The admission path replaces the fragile all-PID census with the system task
upper bound in field four (runnable/total) of Linux proc_loadavg(5).
Every individual real UID's thread count is bounded by that host total at the
sample. The minimum finite inherited NPROC soft/hard limit must be strictly
greater than total + 256; infinity is retained, never replaced or raised.
Both read-only preflight (before ATTEMPT) and admission immediately before
release perform this check. Counts may change between checks: no PID-set or
loadavg equality is required, but each observation must independently leave
the full reserve. This is no atomic reservation against future task creation;
the kernel still enforces NPROC and the owned cgroup must still prove exact
finite pids.max=256. Mixed-UID helpers do not invalidate the upper bound.
Trust qualification is deliberately Linux-specific and fail-closed: the exact
/proc path must be a unique root procfs mount, with no overmount affecting
loadavg. Initial self PID/user namespace identities (4026531836/4026531837),
the full initial UID map and a singleton self NSpid equal to the supervisor PID
are required. PID1 namespace links may be ptrace-protected and are not needed. These
recognized Linux identities are a conservative qualification restriction, not
a portable namespace discovery guarantee. The /proc root and loadavg must
be root-owned; process metadata such as self/status may legitimately be
user-owned. Writable files, symlinks, device/inode drift, malformed or oversized
data, unavailable context or any ambiguous source refuse admission. Reads use bounded no-follow FDs and
qualify the context before and after the sample. Trusted kernel/root mount
administration remains an assumption; hostile root is outside this protocol.
uid_threads() remains the strict optional historical diagnostic, outside
admission. The earlier ENOENT and process creation/end refusals above remain
historical facts. ProcFS.identity(), bootstrap/manager ownership, pidfds,
barrier, cleanup and all cgroup controls retain their contracts. Offline tests
cannot establish CGROUP_NATIVE_PASS; real qualification remains NOT_RUN.
Current global-headroom validation — 2026-10-17
The inert Python suite passes 99 tests (86 historical plus 13 new cases). The unchanged private parent-death observer independently passes its 14 deterministic tests. Neither suite launches a real scope or payload.
Exactly one real read-only preflight ran through Desktop Commander in the
ArchASP app.slice context at 2026-10-17T06:57:21.960482+00:00
(08:57:21 CEST). It passed with inherited NPROC soft/hard 60706/60706,
one global task sample of 1439, reserve 256 and observed remaining
margin 59011. These values describe that sample only; capacity is not
reserved. Only systemctl show child commands were allowed. No ATTEMPT,
scope or payload was created; native qualification remains NOT_RUN.
Two new private 0700 LABs have distinct nonces and exact sealed source copies,
with 47 file identities / 39 ELF records each. Their isolated passive CLI
plans pass through Desktop Commander without launcher_error, writes,
ATTEMPT, socket or result creation. The historical UID-correction LAB inventories
remain unchanged. Private paths, nonces, process identifiers and raw evidence
are retained outside public documentation.
Independent delta review is ACCEPTED, with no blocking finding; it independently reproduces 99/99 tests and 72 targeted tests, with AST invariants and Python style checks passing. The final audit is PASS / ACCEPTED, with no blocking finding: it independently reproduces 99/99 + 14/14 tests, confirms 13 sensitive symbols/classes unchanged by AST, both LAB inventories and fixture hashes, identical historical inventories and passive CLI exit 0 / NOT_RUN. Audited source hashes matched the reviewed-file receipt before this factual documentation closeout.
Disposition: CGROUP_GLOBAL_HEADROOM_READY_FOR_BENIGN_APPROVAL, source-only.
Native qualification remains NOT_RUN; no CGROUP_NATIVE_PASS follows.
Fresh conversational approval is required before nominal or parent-death
execution; EGL needs another fresh LAB, review and approval. STEP20C remains
NEXT and production multi-output remains NOT_YET_SUPPORTED.
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 | IMPLEMENTED_OFFLINE; real qualification NOT_RUN; 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 explains the real-UID thread limit and EAGAIN at creation. The retained optional procfs scan is a strict diagnostic, not an admission prerequisite or atomic reservation: disappearing entries, unreadable status, UID changes or inconsistent thread counts must be reported explicitly. The kernel cgroup v2 contract 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.
Implemented protocol — real qualification pending
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: 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.
The source-only runner implements the following invariants together. Independent code review and native qualification are separate gates:
- 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.
- 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.
- 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.
- 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, trusted global task total, full reserve and effective cgroup properties; minimum finite NPROC <= total + 256 refuses explicitly. Missing or ambiguous host samples cannot prove headroom. No fallback to a larger NPROC, unlimited task count, unconfined launch or new device.
- 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.
- 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 paths are exercised OFFLINE through the actual admission, bootstrap, collection and cleanup functions, using fake procfs, cgroupfs, manager/process adapters and local inert channels. The new test module installs a native-spawn sentinel. Existing historical tests are adapted to schema2. No real scope, bwrap, helper or device is involved; runtime remains NOT_RUN.
| 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
The runner now has separate schema2 benign/EGL kinds and approval flags.
prepare.py --benign --qualification-mode nominal|parent-death creates fresh
0700 qualification LABs and exact sealed passive commands. Independent review
must accept the final scripts and manifests before qualification approval.
The nominal payload is fixed Python; the distinct parent-death LAB deliberately
exits its supervisor after release, with manager runtime/stop limits as the
independent cleanup authority. Its result requires external observation;
a supervisor that has exited cannot certify its own cleanup or write CLEANED.
Its private gate.sock may remain as an artifact. External observation must be
read-only and bind the same invocation, cgroup identity and task lifetimes;
absence of that evidence is not successful cleanup.
After benign qualification passes, prepare another fresh 0700 EGL LAB and manifest. Preserve exact source/library ABI and the closed mount/device/FD policy; obtain review and a separate new conversational EGL approval. No historical reservation or approval is reusable. The old NPROC256 fixture remains preserved in its immutable historical LAB; repository schema2 accepts only the new policy. No EGL retry is authorized by source-only readiness or by benign qualification.
Implementation evidence — 2026-10-17
cgroup_gate.py implements read-only preflight, explicit manager-owned scope
creation, AF_UNIX SOCK_SEQPACKET admission with SO_PEERCRED on both ends,
PID/startticks/pidfd and invocation/inode checks, single release, bounded drain
and owned cleanup. A bounded observer reads recursive emptiness concurrently;
ENOENT/ENODEV is never a substitute for populated=0. Missing evidence produces
CLEANUP_UNPROVEN. The bootstrap closes descriptors above stderr before exec.
The helper is loaded only after its digest check and without bytecode writes; real isolated passive-CLI tests exercise the no-write boundary. Scope properties are TasksMax=256, RuntimeMaxSec=12s, TimeoutStopSec=2s, KillMode=control-group: the manager deadline bounds orphaned execution independently of the supervisor; normal supervision uses a 10-second deadline including cleanup. These limits remain local experimental values to qualify, not measured Mesa requirements. New manifests seal the scope command/properties, scripts, tool ELF identities and dependency closure. No C, compositor, ABI or packaging change is made.
The inert suite passes; exact final counts and source bindings belong to the final review and private handoff after the bounded repairs. A real passive preparation exposed a systemctl RUNPATH resolution defect; the failed fresh LAB and traceback are retained privately and the defect is repaired with explicit ELF resolution tests before final preparation. No native attempt was consumed by that preparation failure.
Pre-admission manager diagnostics are not retained as raw stdout/stderr; the supervisor error is retained. This is a bounded diagnostic limitation, not proof of manager behavior. Neither offline tests nor the manager deadline establish actual cleanup after supervisor death: nominal and parent-death qualification remain NOT_RUN. Final review may establish readiness to request benign approval; this edition does not predeclare that readiness or authorize any scope, benign payload, Bubblewrap or EGL execution.
Historical validation of the preceding 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.
Source-only closeout — 2026-10-17
CGROUP_PROBE_IMPLEMENTED_OFFLINE_READY_FOR_BENIGN_APPROVAL. Repository behavior tests: 75 PASS; Python AST5, local link targets and diff whitespace checks pass. Two fresh benign LABs contain matching source fixtures,47 sealed file identities and39 ELF records each; their exact isolated passive commands pass without writes or ATTEMPT. The actual native runner and external observer remain NOT_RUN.
The single final audit found one BLOCKING in the private parent-death observer: it could accept cleanup without observing an executed benign payload surviving its supervisor. The bounded repair requires exact cmdline/PID/startticks/cgroup, same scope invocation/inode/policy, at least1s observed survival after the supervisor lifetime disappears, then observed recursive emptiness and unit/cgroup absence within14s (global30s). Fourteen deterministic tests pass; independent targeted review accepts this repair. No second general final audit was run. The configuration alone is not claimed to prove RuntimeMax caused termination.
Private LABs retain REPORT_IMPLEMENTATION.md, BENIGN_GATE_APPROVAL.md and EGL_GATE_NEXT.md, reviewed source/diff hashes and test evidence. Nominal and parent-death cases have distinct budgets; the latter needs its concurrent read-only observer. Exact private paths/digests belong to the approval dossier and task handoff, not public unit identifiers. Historical evidence and consumed reservations are unchanged. Real manager admission, parent-death cleanup and Mesa task sufficiency remain unqualified. Separate conversational approval is required before benign acquisition, then another fresh LAB/review/approval before any EGL attempt. STEP20C remains NEXT, multi-output NOT_YET_SUPPORTED.