# Pre9 controlled manual QEMU evidence audit

Date: 2026-08-13

## Scope and evidence status

This report audits the already completed manual run at
`/work/tests-dev/temp/pre9-manual-20260813-run3/`.  No QEMU test was rerun.
The long-lived guard VM (PID 26318, host port 9222) was not touched.

The automated TC004 implementation remains `PENDING_FIX/BLOCKED` in
`/work/tests-dev/fix-todo/tc004-automation.md`.  Run3 is a manual substitute;
it is not evidence that TC004 automation passes.

Run history:

- Run1: infrastructure failure; no retained result directory is available.
- Run2: `INFRA`. Guest SSH authentication failed for Pre7 and Pre8, then the
  host runner was interrupted while preparing Pre9. It produced no DUT result.
- Run3: valid manual run. All three DUTs built, loaded, mounted, executed the
  bounded probes, produced a live SHA stack sample, and cleaned up.

## Verdict

| Question | Verdict | Evidence |
| --- | --- | --- |
| 4 KiB, 16 KiB, 64 KiB and 1 MiB single-syscall read correctness | PASS | Every version returned the requested byte count, `errno=0`, matched the source-range SHA-256, exited zero, and has exactly one target-file `pread()` in truss. |
| First 1 MiB sequential `dd` read | PASS | Every version returned zero and copied 1 MiB within the 20-second bound. This is a bounded smoke check, not proof of one 1 MiB kernel read. |
| Full-file SHA correctness | BLOCKED | No version retained a hash or an exit status for the background SHA. `sha_after` is empty and process disappearance is not correctness evidence. |
| Full-file read liveness/performance | FAIL | After an eight-second observation delay, SHA was still running in the LZMA decode path and had advanced only about 1.0-1.25 MiB. This is unacceptable for the 8 MiB smoke fixture and reproduces across all three versions. |
| Pre7 to Pre8/Pre9 regression attribution | PASS: no Pre9 regression demonstrated | Pre8 and Pre9 have the same `src` tree and identical KLD hash. Pre7 has a different source tree/KLD but shows the same symptom and stack. The evidence attributes the issue to shared behavior, not a Pre9-only change. |

Overall verdict: **PARTIAL**. Bounded reads are correct, but the full-file
correctness verdict is blocked and full-file liveness fails.

## Runner semantics audit

The runner creates a fresh qcow2 overlay per version over the read-only base,
archives the selected repository subtree, builds the KLD in the guest, mounts
the same LZMA fixture, and runs four probes. Each probe invokes a C program that
contains one target-file `pread()` and writes the returned bytes to a file.
The runner then hashes that file and compares it with bytes read from the
uncompressed source fixture.

Important field meanings:

- `commit`: Git tree object for the version directory at the enclosing repo
  HEAD. It is not a standalone commit ID.
- `src_tree`: Git tree object for that version's `src` directory.
- `probes[].elapsed`: host wall time for the complete SSH command, including
  SSH and truss overhead. It is not pure kernel decompression time.
- `metadata.hash_match`: equality between the mounted output-range hash and
  the uncompressed source-range hash.
- `signal=0`: runner shorthand for probe return code zero. It is not a signal
  collected through `waitpid()`.
- `dd_1m`: a userspace `dd bs=1m count=1` result. It does not establish the
  size or count of VOP/kernel reads.
- `sha_pid`: PID printed after starting a background `sha256` command.
- `diagnostic_sample`: process table, kernel stack, descriptor offset, mount,
  md device and dmesg captured after an unconditional eight-second sleep.
- `sha_after`: process status followed by `sha.out` and `sha.err`. Empty output
  means neither a process row nor captured SHA/error output was available.
- `status=PARTIAL`: runner-generated fallback when any bounded probe succeeds.
  It does not mean full SHA correctness passed.

The full SHA was not run under `timeout`. The runner waited eight seconds,
sampled it, sent TERM, slept two seconds, then attempted KILL, and finally read
the output files. No start/end timestamp or exit status was recorded. Therefore
the exact SHA lifetime is unknown; the only defensible timing statement is that
it was still active approximately eight seconds after launch.

`sha_after` contains only `,state=,command=` for all versions. The subsequent
KILL reports `No such process`. This establishes only that the sampled PID no
longer existed after TERM plus the two-second delay. It does not distinguish a
successful completion from TERM handling, and the absent `sha.out` means no
hash can be validated. Process disappearance must not be reported as PASS.

## Controlled identities and setup

The common fixture hashes were:

- Image: `32107a084b27362a093768b88746c37c2e99a74b9f1301a9d4046988479defe9`
- Source: `ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461`

| Version | Repository tree | `src` tree | KLD SHA-256 | Build/load/mount |
| --- | --- | --- | --- | --- |
| Pre7 | `b8f9af2cd55225c4348b79ff5910ae6fc83cd517` | `dc203534d7b5721905f8538026e4c59346106020` | `33a52f2f16a94afda0501d305b238678b96c71e420d4b8bbcbeec6ffcdf1aae5` | PASS/PASS/PASS |
| Pre8 | `1e24c992a5b071e6fdabfdc42d342b6ed5a91cf5` | `cbf93a19df8d1ce2fb66e2aeb7276a4ea6d4dcc2` | `348cb1f5a91d47e1412747d285c795c49375a61a66cfb3d35d22f38ecfde6ea0` | PASS/PASS/PASS |
| Pre9 | `dc0c01157b5c925684bd77c57ed6703904af7453` | `cbf93a19df8d1ce2fb66e2aeb7276a4ea6d4dcc2` | `348cb1f5a91d47e1412747d285c795c49375a61a66cfb3d35d22f38ecfde6ea0` | PASS/PASS/PASS |

Pre8 and Pre9 are runtime-identical for this test according to both the source
tree and the produced KLD hash.

## Single-syscall probes

All probes used offset zero. Times below include SSH/truss overhead.

| Version | Length | Returned | Hash match | Elapsed | Exactly one target `pread()` |
| --- | ---: | ---: | --- | ---: | --- |
| Pre7 | 4 KiB | 4 KiB | yes | 1.299 s | yes |
| Pre7 | 16 KiB | 16 KiB | yes | 1.291 s | yes |
| Pre7 | 64 KiB | 64 KiB | yes | 1.438 s | yes |
| Pre7 | 1 MiB | 1 MiB | yes | 1.432 s | yes |
| Pre8 | 4 KiB | 4 KiB | yes | 1.216 s | yes |
| Pre8 | 16 KiB | 16 KiB | yes | 1.327 s | yes |
| Pre8 | 64 KiB | 64 KiB | yes | 1.430 s | yes |
| Pre8 | 1 MiB | 1 MiB | yes | 1.442 s | yes |
| Pre9 | 4 KiB | 4 KiB | yes | 1.791 s | yes |
| Pre9 | 16 KiB | 16 KiB | yes | 1.341 s | yes |
| Pre9 | 64 KiB | 64 KiB | yes | 1.374 s | yes |
| Pre9 | 1 MiB | 1 MiB | yes | 1.737 s | yes |

The target-file truss lines are preserved in
`pre9-manual-evidence/probe-summary.txt`. Dynamic-loader `pread()` calls are
not counted as target-file calls.

The 1 MiB `dd` results were:

- Pre7: 1 MiB in 0.195526 s.
- Pre8: 1 MiB in 0.179484 s.
- Pre9: 1 MiB in 0.367417 s.

These values are not stable enough for version performance ranking, but all
three bounded operations completed.

## Full SHA sample

| Version | State at sample | File offset | Kernel stack | Final hash |
| --- | --- | ---: | --- | --- |
| Pre7 | running (`RC`) | 1,245,184 | `lzma2_lzma -> erofs_xz_dec_microlzma_run -> lzma_decompress -> z_erofs_decompress -> z_erofs_do_read -> z_erofs_read_uio` | absent |
| Pre8 | running (`RC`) | 1,150,976 | same | absent |
| Pre9 | running (`RC`) | 1,044,480 | same | absent |

The stack is direct evidence of active CPU-side decompression, not an EROFS
lock wait. It does not by itself prove why decompression is slow. Combined with
the static review, the leading explanation remains repeated full mapped-extent
decompression as `z_erofs_read_uio()` segments reads at `MAXPHYS`. There is no
direct evidence here of an XZ infinite loop or an EROFS lock deadlock.

## Infrastructure integrity and cleanup

The base image SHA-256 before and after run3 was identical and matched the
expected value:

`67f359621f23a1d745f0889370cbb99a096cee3e99a0b2f3bb18fc7a91bf6fef`

For each version, `umount`, `mdconfig -d` and `kldunload` returned zero. Each
QEMU PID was no longer alive and each overlay was deleted. The run3 result set
therefore supports successful test-VM cleanup. This report does not infer the
state of the unrelated long-lived guard VM and did not inspect or modify it.

## Required follow-up

A supplemental manual test is still required if full-file correctness must be
closed. It should run one full SHA with an explicit wall-clock deadline, record
start/end timestamps and exit status, capture `sha.out` before cleanup, compare
the hash to the source fixture, and separately record whether TERM/KILL was
used. Until then, full-file SHA correctness remains `BLOCKED`.
