From ccd534132db6eeaed9a48ae0b84237880f632ac2 Mon Sep 17 00:00:00 2001 From: fy59 Date: Sat, 17 Oct 2026 06:46:58 +0200 Subject: [PATCH] docs(mosaic): record Gate 2 V5 allocator failure and publication policy --- AGENTS.md | 13 +- docs/README.md | 7 + .../STEP20C_HEADLESS_ALLOCATOR_TRIAGE.md | 91 ++++++ docs/design/STEP20C_HEADLESS_BOOT_GATE2.md | 309 ++++++++++++++++++ 4 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 docs/design/STEP20C_HEADLESS_ALLOCATOR_TRIAGE.md create mode 100644 docs/design/STEP20C_HEADLESS_BOOT_GATE2.md diff --git a/AGENTS.md b/AGENTS.md index 0ae1ba0..cab9680 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -97,6 +97,10 @@ Resolve contradictory canonical documents **before** implementing behavior. 3. Use existing C, Sway/wlroots/SceneFX conventions. Keep ownership/lifetimes, container identity, popup hierarchy, pending/current state and output coordinates explicit. Add regression tests for changed behavior. + For Python probe tools, use readable PEP 8 formatting, short functions and + docstrings explaining effects and refusals. Comment design/ownership + invariants and explicit error handling; keep offline tests reproducible and + independently reviewed without starting native workloads. 4. Build and test in isolated build directories; never install, reload, restart or replace the **running desktop session**. Never operate on the live Sway IPC socket. Headless tests must use their own `XDG_RUNTIME_DIR`, @@ -146,7 +150,14 @@ Resolve contradictory canonical documents **before** implementing behavior. checksum. The exporter validates before atomic replacement and must leave the previous artifact intact on failure. Never force-apply hunks. - Do not modify git history, branches, remotes or package version/pins without - an explicit scoped reason. **No commit or push unless the user requests it.** + an explicit scoped reason. For this repository, Codex must commit each + coherent batch after documentation, applicable tests and independent review, + then push origin followed by github and verify that both remote branch SHAs + match the intended commit. This permanent user instruction excludes force + pushes, upstream pushes, history rewriting, other repositories, private + evidence, personal identifiers, secrets/credentials, binaries, raw logs, + large files and unvalidated PASS claims. It grants no production change, + native execution or sandbox expansion beyond the current task's scope. ## Required handoff after each task diff --git a/docs/README.md b/docs/README.md index ca65f88..d72c7bb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,12 @@ # Documentation +[Gate 2 headless boot](design/STEP20C_HEADLESS_BOOT_GATE2.md) records the single +V5 native failure: EXPERIMENTAL / PRISTINE_ALLOCATOR_BLOCKED / CORE_NOT_RUN, +without private IPC or rendering proof. The [allocator triage](design/STEP20C_HEADLESS_ALLOCATOR_TRIAGE.md) +distinguishes source inference from observed failure; any new native experiment +requires separate approval. STEP20C remains NEXT; production multi-output +NOT_YET_SUPPORTED. + The [CORE-C V2 follow-up decision V4](design/STEP20C_COREC_V2_INTEGRATION_DECISION_V4.md) records PARTIALLY_ADVANCED private evidence: five consumed runs, 32 output captures, migration/refocus and root/nested OOM SAMPLED_ONLY / PARTIALLY_FEASIBLE. diff --git a/docs/design/STEP20C_HEADLESS_ALLOCATOR_TRIAGE.md b/docs/design/STEP20C_HEADLESS_ALLOCATOR_TRIAGE.md new file mode 100644 index 0000000..e0211df --- /dev/null +++ b/docs/design/STEP20C_HEADLESS_ALLOCATOR_TRIAGE.md @@ -0,0 +1,91 @@ +# STEP20C — headless allocator triage + +Status: **EXPERIMENTAL / PRISTINE_ALLOCATOR_BLOCKED / CORE_NOT_RUN**. +This is a source-based triage of the [V5 native failure](STEP20C_HEADLESS_BOOT_GATE2.md), +not a renderer repair or a new native authorization. STEP20C remains NEXT; +production multi-output NOT_YET_SUPPORTED. No compositor, build, IPC, device, +scope or production change is authorized by this note. + +## Observed result and source boundary + +The single approved V5 pristine run failed after 0.361879 seconds, before +private IPC, with oracle null. Its first allocator error was ENOENT opening +`/dev/udmabuf` in the private `/dev`; allocator creation then failed. +CORE has no ATTEMPT and is CORE_NOT_RUN. The preceding Mesa software-rendering +warning is not established as fatal. No effective renderer/backend capability +masks, EGL provider or selected native device identity were recorded. + +Static inspection used pristine source archives without `.git`, at these +retained pins. Twelve inspected SceneFX/wlroots files match the corresponding +Git commit objects; this proves neither binary reproducibility nor live capabilities: + +- [SwayFX fd71a6bdc061bd633b488ae7b83e8a6981d22f86](https://github.com/wlrfx/swayfx/blob/fd71a6bdc061bd633b488ae7b83e8a6981d22f86/sway/server.c#L283): + `server.c:283` directly calls `fx_renderer_create`; `:305` calls the allocator. +- [SceneFX 37ccd723bef49e6891156ffafce8f549f01446cc](https://github.com/wlrfx/scenefx/tree/37ccd723bef49e6891156ffafce8f549f01446cc): + `render/fx_renderer/{util.c,fx_renderer.c,fx_framebuffer.c,fx_offscreen_buffers.c}` + and `render/egl.c` govern the FX path described below. +- [wlroots d783533489e1f75d6886c2ab5c5960090ef268f8](https://gitlab.freedesktop.org/wlroots/wlroots/-/tree/d783533489e1f75d6886c2ab5c5960090ef268f8): + `render/allocator/{allocator.c,shm.c,udmabuf.c}`, `render/pixman/renderer.c`, + `util/shm.c` and `backend/{headless,multi}/backend.c` govern selection. + +## Allocation contract inferred from those sources + +`open_preferred_drm_fd` honors `WLR_RENDERER_FORCE_SOFTWARE=1` before render-node +selection, preserving FD -1 on this creation path. SceneFX then calls +`wlr_egl_create_with_drm_fd`; its negative-FD path selects an EGL device with +`EGL_MESA_device_software` and allows software. GBM fallback requires FD >= 0. +This explains the intended software route, not the effective V5 EGL identity. + +`fx_renderer_create_egl` initializes `render_buffer_caps` to DMABUF only. +SHM/DATA_PTR texture import is a separate capability, not SHM render-target +support. `fx_framebuffer.c:138` rejects targets without `wlr_buffer_get_dmabuf`. +Headless advertises DMABUF, SHM and DATA_PTR; the multi backend intersects +capabilities of presenting backends. Actual V5 masks remain unmeasured. + +Allocator predicates test **nonempty bit intersections**, not containment of +all bits in a combined mask. In source order, with B = backend caps, +R = renderer render-buffer caps, D = selected allocator DRM FD: + +| Candidate | Eligibility predicate, before compiled-feature/create failures | +| --- | --- | +| GBM | `(B & DMABUF) && (R & DMABUF) && D >= 0` | +| SHM | `(B & (SHM \| DATA_PTR)) && (R & (SHM \| DATA_PTR))` | +| DRM dumb | `(B & (DMABUF \| DATA_PTR)) && (R & (DMABUF \| DATA_PTR)) && D >= 0 && drmIsMaster(D)` | +| UDMABUF | `(B & (DMABUF \| SHM)) && (R & (DMABUF \| SHM)) && D < 0` | + +Thus headless + FX DMABUF-only + negative FD skips SHM and reaches UDMABUF +when compiled in. `udmabuf.c:146` opens `/dev/udmabuf` with `O_RDWR | O_CLOEXEC`. +That source path is consistent with the observed failure; it is not a runtime +capture of every predicate. Host metadata reports root:kvm mode0660 and the +user outside kvm. Metadata grants no authority to open or expose the device, +change permissions/group membership, or broaden the sandbox. + +## Decision matrix + +| Route | Evidence | Hypothesis / future test | Risk / disposition | +| --- | --- | --- | --- | +| Existing software FX | Negative-FD EGL route; DMABUF render targets; UDMABUF ENOENT observed | Measure provider, FD and format/capability sets in a separately approved FX-only helper | Software EGL alone does not supply a compatible allocator; BLOCKED | +| Pixman + SHM | Pixman advertises DATA_PTR; SHM allocator uses `shm_open`, unlink, `ftruncate`, `mmap` | A writable private `/dev/shm` could serve this distinct allocator route without a physical device | No FX compatibility proof; not a substitute pristine control | +| `WLR_RENDERER=pixman` alone | SwayFX directly creates SceneFX; `fx_get_renderer` asserts FX, including offscreen buffers | Any private renderer/compositor change needs a separate design and approval | Environment override cannot replace this FX contract | +| Host UDMABUF / DRM | Device-opening source requirement; no approved exposure | No device experiment proposed here | Sandbox expansion excluded; no workaround promised | + +## Smallest next experiment to authorize + +Offline validation passes eight cases executing the verbatim allocator-selection +function compiled as C17 with inert callees, plus six static FX/Pixman/call-site/ +framebuffer assertions. No device was opened and no EGL was initialized. These +checks validate source selection, not effective EGL identity or target allocation. + +The next runtime decision would require a **new explicit approval** for one +small EGL/FX-only helper, without compositor, IPC, systemd scope or host-device +exposure. Before execution, review exact pinned sources/library identities, +helper code, fixed deadline, output/resource bounds and cleanup. Use fresh +private temporary/runtime storage and private `/dev/shm`; never reuse a V5 LAB +or reservation. Record EGL provider/software-device identity, FD < 0, +backend/render/texture caps and render-target formats; refuse device fallback, +missing identity or incompatible allocation. No retries or automatic escalation. + +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. diff --git a/docs/design/STEP20C_HEADLESS_BOOT_GATE2.md b/docs/design/STEP20C_HEADLESS_BOOT_GATE2.md new file mode 100644 index 0000000..9725e75 --- /dev/null +++ b/docs/design/STEP20C_HEADLESS_BOOT_GATE2.md @@ -0,0 +1,309 @@ +# STEP20C — Gate 2 headless boot preparation + +Status: **EXPERIMENTAL**. V4 pristine: **FAIL before compositor**; +V4 CORE: **NOT_RUN**. V5 native: **PRISTINE_ALLOCATOR_BLOCKED / CORE_NOT_RUN**. +The separately approved single V5 sequence consumed its pristine reservation; +CORE was never attempted. No sequence PASS, private IPC or rendering proof was +obtained. Historical preparation verdicts below are pre-attempt evidence. +Any future native attempt requires a new LAB, budget and explicit approval. + +## V5 first native outcome — 2026-10-17 + +The reviewed V5 fixture ran once from the approved app.slice launcher after +preflight of 12 fixture hashes, 92 manifested files and 98 ELF identities. +The supervisor returned `REFUSED: pristine failed: CORE NOT_RUN`. +The retained pristine receipt records verdict FAIL, elapsed 0.361879 seconds +and oracle null. The scope executed the Python child and native pristine +SwayFX binary: unlike V4, the failure occurred after compositor execution. +The mandatory `PWD=/tmp` correction passed that earlier environment guard. + +The relevant diagnostic messages, in order, are: + +```text +MESA-EGL: warning: Not allowed to force software rendering when API explicitly selects a hardware device. +[wlr] [render/allocator/udmabuf.c:148] Failed to open /dev/udmabuf: No such file or directory +[wlr] [render/allocator/allocator.c:168] Failed to create allocator +[sway/server.c:308] Failed to create allocator +CHILD REFUSED: compositor exited before private IPC +``` + +The immediate failure is allocator creation with the host device absent from +the private /dev. The Mesa warning is not established as fatal. Source analysis +must distinguish software EGL rendering from allocation of render targets; +no effective capability masks were logged. See the +[offline allocator triage](STEP20C_HEADLESS_ALLOCATOR_TRIAGE.md). +There is no private IPC, output oracle or rendering evidence. CORE is NOT_RUN, +with no ATTEMPT receipt; no result transfers to that binary. + +Post-attempt independent observations found both V5 scopes not-found/inactive, +their cgroups absent and the pristine runtime empty. Stable SwayFX, QuickShell +and greetd remained active with their original process identities. These are +historical post-run observations, not authority to manipulate those services. +The V5 LAB, raw stdout/stderr, result, units and reservation ledger are sealed +historical evidence: never edit or reuse them and never retry pristine there. +Only this sanitized documentary summary is published; raw receipts stay private. + +## V4 native failure and prospective V5 repair — 2026-10-17 + +The approved V4 pristine attempt at 06:09 CEST failed with +`REFUSED: pristine failed: CORE NOT_RUN`. Its raw +`logs/pristine.stderr.raw` contains +`CHILD REFUSED: unexpected sandbox environment`. The strict comparison +`dict(os.environ) == CHILD_ENV` rejected the Python child before its first +compositor Popen. No pristine or CORE compositor, native IPC/socket or rendering +was executed. The pristine reservation is consumed and must never be retried +in `/var/tmp/swayfx-mosaic-step20c-gate2-prep-qglkg45r`; CORE was not reserved. +The retained result records unit LoadState=not-found / ActiveState=inactive +and the cgroup was absent. Historical preparation verdicts below describe +pre-attempt evidence only; they do not requalify this failure. + +The harmless post-failure Bubblewrap diagnostic at +`/var/tmp/swayfx-mosaic-step20c-gate2-env-diagnostic-uh25ciwe/RESULT.json` +returned 0 and measured only `extra_keys=["PWD"]`, `PWD="/tmp"`, no missing +keys or changed shared values, and strict_equal=false. The local bwrap(1) +contract preserves PWD through --clearenv. This is a reproduced environment +cause, not a second native attempt. + +V5 will use a new exclusive mode0700 LAB, fresh scope identities and empty +reservation ledger. Its sole functional correction makes `PWD=/tmp` mandatory +in CHILD_ENV and explicitly emits `--setenv PWD /tmp` after --clearenv, keeping +--chdir /tmp and the complete dictionary equality guard. V4 and older LABs, +including raw failures, remain byte-for-byte unchanged. Offline tests and a +harmless Bubblewrap child must pass, as must the read-only plan from Desktop +Commander under app.slice and independent review. No compositor may run in +this correction mission; a new conversational approval is required for V5. +STEP20C remains NEXT; production multi-output NOT_YET_SUPPORTED. + +## Scope and authority + +Prepare a symmetric comparison of the retained pristine SwayFX 0.6 and private +Mosaic CORE-C V2. The supervisor stays outside the sandbox; its child enters an +ephemeral delegated systemd user scope under app.slice, then Bubblewrap, then +the fixed binary and private IPC checks. Reuse the combined benign proof at +`/var/tmp/swayfx-mosaic-step20c-combined-benign-z4wk7gog`: its 22/22 checks +qualify that benign child only. Do not reopen historical scientific ledgers. + +The original preparation scope excluded changes to production source, C API/ABI, +SceneFX patches, packages, UX, Stable compositor, QuickShell, greetd and UWSM. +It prohibited installation, session switching, compositor execution (including +--version) and live Sway IPC. +The later single V5 native sequence had separate approval, now consumed. +The current documentation-only mission authorizes a reviewed commit and pushes +to origin then github; it authorizes no new compositor or sandbox expansion. +Architecture and UX contracts remain unchanged. STEP20A DONE; STEP20B +EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED; production multi-output +NOT_YET_SUPPORTED. Headless software evidence cannot qualify DRM/GPU, G4, +fullscreen, buffer lifetime, complete effects/damage or daily use. + +## Exact identities and passive preflight + +| Profile | Retained ELF under /var/tmp/swayfx-mosaic-corec-v2-i516p6po | SHA256 | Build ID | +| --- | --- | --- | --- | +| pristine | controls/pristine/sway | b7fa32116e3c42706b9b02da7df12f27e76d82dd253deb8bdec81519eed145f6 | ba9751c82e73d4043b7651354f20936d070c2227 | +| core | builds/core-mosaic/sway/swayfx-mosaic | 91f48aae2675331e0a2c60ec2fedf983745c521c75232c704a6516a4140801fb | d5d304533f2c8db9d738365e9bd7f2c96779ef8d | + +Pristine dynamically needs libscenefx-0.5.so. CORE embeds private SceneFX and +must have no SceneFX DT_NEEDED. Both use system libwlroots-0.20.so with SHA256 +`7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837`. +The main build and diagnostic OOM/GPU variants are not substitutes. + +Inspect UID, mode, link count, size, SHA, Build ID, ELF class/machine, interpreter, +DT_NEEDED, RPATH/RUNPATH and symbols with readelf, never ldd or the target loader. +Resolve the transitive library closure passively under /usr/lib, recording real +paths and hashes. Inspect EGL vendor JSON, swrast DRI module, XKB and font data +separately: DT_NEEDED is not the dlopen/data closure. Missing or drifted inputs +are refusals, not permission to rebuild, install or change the binary. +Local ABI evidence is exact-pair only; passive resolution does not prove live +relocation, renderer initialization or future update compatibility. + +## Environment and containment contract + +The manager control plane alone uses an explicitly reconstructed user bus +address. Desktop Commander must be measured under user@1000.service/app.slice; +a launcher under session-6.scope is rejected. No PID from a historical report +is an authority for the new run. Verify manager Delegate=yes, active app.slice, +writable common-ancestor cgroup.procs and memory/pids controllers before spawn. + +Bubblewrap uses --unshare-user --unshare-all --disable-userns, --new-session, +--die-with-parent, --clearenv; /usr read-only, /bin /lib /lib64 links into /usr, +private /proc and /dev, private tmpfs /tmp and /run. Bind only the sealed LAB +input read-only and run-specific LAB output/runtime writable. No host HOME, +/run/user, /sys, display, seat, bus, configuration, network namespace, +/dev/dri or /dev/input is exposed. Close all inherited FDs other than explicit +stdio; stdio uses /dev/null and bounded pipes, never desktop sockets or a TTY. +The private runtime directory is UID1000 mode0700; all IPC paths belong there. + +Do not bind host /etc wholesale. Candidate minimal data are exact EGL vendor +JSON (normally under /usr/share/glvnd/egl_vendor.d), /usr/share/X11/xkb and +/usr/share/fonts. Fontconfig may require a private minimal fonts.conf referring +only to /usr/share/fonts and LAB cache, supplied through FONTCONFIG_FILE. +No host NSS/password, machine-id, seat or ld.so cache is needed by assumption: +any missing requirement must be observed and reviewed, never silently exposed. +/proc/sys and same-UID hostile peers remain security limitations, not a claim +of a hardened general execution service. + +SceneFX directly calls fx_renderer_create, not wlr_renderer_autocreate. +WLR_RENDERER=pixman is therefore not evidence of effective Pixman. The source +supports WLR_RENDERER_FORCE_SOFTWARE=1, passing DRM FD -1 into EGL software +device selection. Candidate environment: WLR_BACKENDS=headless, +WLR_HEADLESS_OUTPUTS=2, WLR_RENDERER_FORCE_SOFTWARE=1, +WLR_RENDERER_ALLOW_SOFTWARE=1, LIBGL_ALWAYS_SOFTWARE=1, XWAYLAND disabled by +configuration. Effective EGL/renderer/allocator success remains NOT TESTED. +No automatic GPU fallback or host device exposure is allowed. + +## Proposed budget, supervisor and oracle + +These are bounds to approve later, not consumed reservations: exactly one +pristine attempt, then exactly one core attempt only if pristine passes all +checks; no retry. At most two 800x600 headless outputs; 30 seconds per run, +TasksMax=64, MemoryMax=1GiB, MemorySwapMax=0, TimeoutStopSec=2s, +KillMode=control-group. Both stdout and stderr are drained concurrently with +16MiB maximum per channel; limit overflow or partial output fails the run. +Capture budget zero for the minimal IPC boot. Missing proof never means PASS. + +Before spawn, journal exact argv, hashes, unit name, unique description, fresh +attempt identity and expected cgroup. Reject existing unit, directory, socket +or attempt receipt. Persist atomically with fsync before execution. Record +Popen-owned PID, startticks and pidfd; match manager InvocationID, description +and ControlGroup before any stop. Never kill by guessed/recycled PID, never +stop a foreign unit, and never adopt a pre-existing unit. Unknown ownership +fails cleanup rather than widening authority. All pidfds/pipes must close. +On timeout/cancellation stop only the proven invocation; verify collection, +empty cgroup, exited owned processes and absent LAB sockets. Do not rerun. + +Inside the sandbox the minimal client is /usr/bin/swaymsg with an explicit +-s /runtime/sway-ipc.sock; get_version, get_outputs and get_tree are read-only. +The bootstrap must verify socket UID/type/path and the private Wayland socket; +no discovery via inherited SWAYSOCK is allowed. Require exactly two active +800x600 outputs and a valid tree/version for each binary; compare symmetric +fields without requiring equal Mosaic-specific metadata. Request exit only +on the proven private socket, distinguish an exit-IPC disconnect from the +compositor exit status, and independently verify cleanup. No popup, fullscreen, +G4 or graphics capture is required. An empty workspace does not qualify Mosaic +layout behavior, only the minimal native boot/IPC path. + +## Refusals, tests and decision + +The source-only fixture lives in the new private LAB until independent review +justifies publication. Its default is a read-only plan: no directory creation, +manager mutation, Bubblewrap, socket connection or native target execution. +A future launch requires its dedicated flag after separate conversational +approval; tests cannot grant that approval. Test production predicates with +fake manager/cgroup/procfs and private temporary files, including session scope, +wrong ELF/ABI/Build ID, inherited environment/socket, forbidden mounts, unit +collision, InvocationID mismatch, recycled PID/startticks, timeout and partial +output. No real scope or compositor in offline tests. + +| Gate | Required evidence | Preparation status | +| --- | --- | --- | +| Exact binaries and passive closure | ELF_DEPENDENCIES.json and raw inspection | PASS, passive only | +| Combined benign isolation | Prior immutable receipt and 22 checks | PASS, benign only | +| Current launcher and environment | Fresh observations and frozen sandbox spec | PASS for preparation | +| Fixture and refusal tests | Frozen source hashes, raw tests, independent review | PASS, V4 / 27 offline tests | +| V4 pristine native boot/IPC/cleanup | One reservation consumed; refused before compositor | FAIL before compositor | +| V4 CORE native boot/IPC/cleanup | Pristine failed; no reservation | NOT_RUN | +| V5 pristine native boot/IPC | One approved reservation consumed; allocator failure before IPC | PRISTINE_ALLOCATOR_BLOCKED | +| V5 CORE native boot/IPC | Pristine failed; no ATTEMPT | CORE_NOT_RUN | +| V5 post-attempt cleanup | Independent scope/cgroup/runtime observations | Absent / empty, sampled | +| Physical GPU, FS/G4 and full STEP20C | Outside this tranche | NOT TESTED | + +The private LAB must contain GATE2_PREP_REPORT.md, ELF_DEPENDENCIES.json, +SANDBOX_SPEC.md, NATIVE_RUN_REVIEW.md, APPROVAL_GATE2.md, validation logs, +SHA manifest and before/after observations. Preserve raw failures. Record the +final offline verdict here after validation. Only all closed preparation gates +permit GATE2_NATIVE_BOOT_READY_FOR_APPROVAL; otherwise use +GATE2_BOOT_PREP_BLOCKED_WITH_EVIDENCE. Those labels describe preparation only; the later V5 native failure above +supersedes NOT_RUN for pristine, without qualifying boot/IPC. + +## Historical preparation evidence — before the V4 attempt + +The private source-only fixture is retained in +`/var/tmp/swayfx-mosaic-step20c-gate2-prep-qglkg45r/fixture`, not published as a +production tool. FIXTURE_SHA256SUMS_V3 binds eight files. Independent passive +review rechecked 96 main ELF entries, 46 supplemental tool entries (overlapping), +1409 data files and all 14 files bound by the prior combined benign receipt. +The current fixture inspects 98 unique ELF files, including its Python child. +Retained source manifests match 85 SceneFX and 417 Mosaic files. No rebuild +or new binary reproducibility claim follows. + +The default V3 plan passes from Desktop Commander under app.slice (exit0, +launcher_error null) and refuses the Codex session-scope launch path. Both +remain PREPARATION_ONLY / native NOT_RUN. The planned native scope uses +RuntimeMax25s within the supervisor's 30s total bound, preserving cleanup time. + +V2's independent review found an uncollected systemctl-stop auxiliary on its +timeout path. The bounded V3 repair reserves one second within the existing +deadline and finalizes only the exact owned Popen. Its delta review passes; +five independently executed mocked methods pass, and the separate tester +passes all 23 offline tests plus eight before/after fixture hashes and AST/local +style checks. Raw initial failures and V2 evidence remain retained. Ruff/Black +are unavailable and were not installed. Native boot, new sandbox mounts, +EGL/renderer/allocator and real cleanup remain NOT TESTED. + +The LAB contains the sandbox specification, exact future approval commands, +passive inventories, raw logs, review records and final preparation report. +The frozen fixture README records its pre-review state; NATIVE_RUN_REVIEW.md +and GATE2_PREP_REPORT.md carry later review outcomes without rewriting that +sealed source set. The final preparation verdict is recorded below after the +pre-freeze audit. No native approval or reservation has been consumed. + +### Historical V4 preparation outcome (superseded by native refusal) + +**GATE2_NATIVE_BOOT_READY_FOR_APPROVAL**. Native Gate 2 **NOT_RUN**. +The unique deep audit on V3 found an additional passive-child finalization +defect in inspect_command on exceptions/cancellation. The single bounded final +repair V4 reserves 0.25s inside the existing deadline, finalizes only its owned +Popen and reports unproven collection explicitly. Four targeted methods were +independently exercised; bounded delta review passes and the separate tester +passes all 27 tests and eight V4 hashes before/after. The default V4 plan +passes through Desktop Commander with no native execution. Historical V2/V3 +failures and the failed V3 audit remain intact; no second deep audit occurred. + +Two NON_BLOCKING observations persist: internal IPC/exit auxiliaries rely on +the enclosing scope/PID namespace after exceptional local teardown, and the +full attempt chain is not simulated end-to-end. They are not runtime evidence. +The LAB's APPROVAL_GATE2.md binds the exact V4 fixture, future command and +unchanged two-profile identities. No native authorization follows from this +preparation verdict; the first native attempt still requires separate approval. + +## Historical V5 correction evidence — before the native attempt + +New exclusive mode0700 LAB: +`/var/tmp/swayfx-mosaic-step20c-gate2-v5-ge8wddpa`. +The imported V4 fixture passes all eight source hashes. The sole functional +change adds mandatory `PWD=/tmp` to CHILD_ENV, automatically emitted as an +explicit --setenv after --clearenv. child.py, its complete environment equality +and --chdir /tmp remain unchanged. The new LAB and scope identities are V5-only; +no historical ledger was imported or reopened. + +The targeted suite passes 31 tests (27 inherited, four new). The harmless +Bubblewrap child returns 0, strict_equal=true, PWD=/tmp, cwd=/tmp, no extra or +missing keys and no changed shared values. It exercises child.main's guards +with every Popen replaced by a refusal at the native boundary. The sandbox +argv/environment prefix is exact V5; only the final Python script/profile is +replaced with a harmless -c payload. All seven namespaces differ from host; +mount/device/FD/network checks pass. The owned child is reaped with pidfd +confirmation, transient directories removed and no remaining sockets. +This qualifies the environment correction only, not a native boot. + +The read-only plan passes from Desktop Commander under app.slice (rc0, +launcher_error=null; 98 ELF inspected passively). The Codex session-scope +refusal is retained (rc1). No real scope or IPC was created by these checks. +HISTORICAL_COMPARISON.json confirms the five historical LAB trees unchanged. +Before the native attempt, V5 receipts ATTEMPT-pristine.json and +ATTEMPT-core.json were absent. The later pristine reservation is consumed. + +REPORT_V5.md, APPROVAL_V5.md, ENV_DIFF_V5.json, BENIGN_ARGV_V5.json, +FIXTURE_SHA256SUMS_V5 and raw logs are retained in that LAB. The independent audit passes with no BLOCKING finding. Its NON_BLOCKING +harmless-helper pidfd acquisition cleanup observation received a bounded +repair and two mocked failure subcases; the targeted final suite now passes +32 test methods. A fresh harmless Bubblewrap run also passes on the repaired +helper. The independent repair-delta review passes (12 source hashes, one mocked +method/two failure subcases). The final source/evidence manifests and +FINAL_VALIDATION_V5.json retain the closed checks. +Verdict: **GATE2_V5_READY_FOR_NEW_NATIVE_APPROVAL**. +The read-only reviewer return is transcribed by the parent in +review/REPAIR_REVIEW_V5.md; the unique audit report remains intact. At that preparation stage no native V5 execution was authorized. +The separately approved V5 attempt is recorded above: pristine allocator FAIL, +CORE NOT_RUN. V4 pristine remains FAIL before compositor; V4 CORE NOT_RUN. +STEP20C remains NEXT; production multi-output NOT_YET_SUPPORTED.