docs(mosaic): record Gate 2 V5 allocator failure and publication policy

This commit is contained in:
fy59 2026-10-17 06:46:58 +02:00
parent 7216eb5518
commit ccd534132d
4 changed files with 419 additions and 1 deletions

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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.