# AppImage shutdown proof

Real source-built x86_64 Linux AppImages, one per exact commit in build-provenance.json.
Built with `T3CODE_DEV_SEED=0 vp run dist:desktop:linux` in separate installed worktrees.
Linux x86_64, Xvfb 1920x1080, openbox, D-Bus, FUSE, light theme, fresh isolated HOME,
XDG directories and T3 home. No real projects, accounts, agents or copied database.
A hostnamectl shim supplies the synthetic display label "AppImage evidence".

## Driver

Install Python tkinter, ImageMagick, ffmpeg, openbox, wmctrl and headless-desktop.
Place driver.py and pageout.py together. The page-out helper needs passwordless sudo
for process_madvise permission. Use your AppImage's absolute path:

```sh
headless-desktop python3 driver.py /absolute/path/base.AppImage before 5f85768394f417f5bd098ca7fe4fc41ef07a7cda held /absolute/path/captures
headless-desktop python3 driver.py /absolute/path/head.AppImage after 559f8e70f2d70efaff391d945857d69fcd4c4bff held /absolute/path/captures
```

Use an absolute captures directory when replaying. Each run writes a new timestamped directory.

1. Set PR_SET_CHILD_SUBREAPER and launch the actual mounted AppImage with
   --no-sandbox --disable-gpu; wait for its window and backend startup.
2. Identify Electron main, both Chromium zygotes, and the separate AppImage FUSE runtime.
3. Send WM_DELETE_WINDOW with wmctrl (normal last-window close).
4. PTRACE_O_TRACEEXIT observes main after its userspace exit hooks. At that boundary,
   SIGSTOP any still-live zygotes, then immediately continue main's kernel exit.
   This makes the intermittent race inspectable; it does not stop zygotes before shutdown.
5. Reap the mount runtime and inspect /proc/self/mountinfo plus each known zygote's state.
6. If two zygotes survive, SIGKILL the companion; use process_madvise(MADV_PAGEOUT)
   on the survivor's t3code executable mappings; SIGCONT the survivor and record its wait status.
   This intentionally simulates executable page pressure after unmount.
7. Record the same timeline and procedure on head; there are no live zygotes at step 4,
   so no target remains for page-out. Final stills are verified pixel-identical 500 ms apart.

## Observations

Before: main and runtime exited 0; mount absent; two stopped live zygotes remained.
Page-out processed 228929536 bytes without errors. Resuming the surviving zygote
produced SIGBUS (wait exit -7, WCOREDUMP true).
After: both zygotes released mappings before main's kernel exit, with SIGKILL exit -9;
main and runtime exited 0; no live zygotes remained after unmount and no SIGBUS occurred.
Every observed head wait status had WCOREDUMP false.

The videos contain the actual app startup, wmctrl close and live observer output.
The trace PNGs crop only the observer from the unchanged full-screen final still.
JSON files preserve observed events and kernel wait statuses. Polling timestamps
are observations, not high-precision scheduler ordering; the stopped survivor state,
mount absence and kernel wait signal are the decisive evidence.

ARM64/Hyprland and an uninstrumented naturally intermittent SIGBUS were not tested.
WCOREDUMP is the kernel wait-status flag; an apport/core archive was not inspected.
Prepared using gpt-6.1-sol through the Codex harness.
