# P15-032 / G09 vnode-backed image gate

## Decision rule

This gate is source-preparatory only.  It does not link its model into EROFS and
does not authorize B39 merely because a declared state sequence looks safe.  It
pins and extracts the relevant FreeBSD 15 vnode, pager, mount, GEOM, md(4),
nullfs, unionfs, tarfs, and deadfs contracts, compiles an owned-temporary C
lifecycle model generated from `P15-032-input.json`, and rejects adversarial
false-GO mutations.

The runner exits with:

* `0`: gate GO;
* `10`: gate STOP;
* `20` or another nonzero value: runner failure;
* `124`: absolute gate timeout.

All phases have absolute deadlines.  The generated C source and its output are
copied into the requested evidence directory; the temporary executable is
removed by the runner trap.

## Closeable local vnode contract

The pinned FreeBSD sources support a coherent local lifecycle:

1. Use an explicit `vnode:/absolute/path` tag; GEOM remains explicit or legacy
   GEOM syntax.  A mount and all `device.N` sources use one backend kind.
2. Resolve with `namei`, require `VREG`, retain vnode identity with
   `vn_open_vnode(FREAD)`, retain the mounter credential with `crhold`, and use
   that credential for every source `VOP_READ`.
3. Apply `VOP_SET_TEXT` before first I/O.  The default write-count contract
   rejects existing or later writers with `ETXTBSY`; retain a size snapshot and
   convert a short source read into `EIO`.  Rename or pathname replacement does
   not change the held vnode identity.
4. Read synchronously into `UIO_SYSSPACE` under a source range lock and source
   vnode lock.  FreeBSD's old vnode-pager fallback drops the VM object write
   lock before `VOP_READ`, so no user-buffer pager fault is introduced by this
   read shape.
5. Register the source mount with `vfs_register_upper_from_vp` before first I/O.
   On unmount, reject new I/O, drain delayed and in-flight completions, `vflush`,
   unregister the upper mount, unset text, close the vnode, release the held
   credential, and then release the final mount-private reference.  A forced
   dead source returns `ENXIO` rather than silently changing identity.
6. Keep GEOM open/read/close and vnode open/read/close as disjoint tagged-union
   branches.  This preserves the existing GEOM and multidevice behavior.

The generated model checks this order and independently removes each local
invariant to ensure that every mutation is rejected.

## Blocking FreeBSD contract

The required pre-I/O self and ancestor check is transitive across VFS and GEOM,
not only across pathname aliases:

```
regular source vnode
  -> source filesystem
  -> md(4) GEOM provider
  -> md_s.s_vnode.vnode
  -> another filesystem vnode
```

The final identity edge is not available through a generic, identity-preserving
FreeBSD API:

* `VOP_GETLOWVNODE` exposes vnode-stack aliases such as nullfs and unionfs, but
  it does not traverse a filesystem's GEOM storage dependency.
* `vfs_register_upper_from_vp` pins the immediate source vnode mount and orders
  its unmount, but it does not register hidden GEOM-to-vnode backing edges.
* `md(4)` stores the held backing vnode in the private `struct md_s` defined in
  `md.c`.  Its GEOM object exposes only `void *softc`; dump configuration exposes
  a pathname, not a held vnode identity.
* Re-resolving that pathname fails the rename/replace invariant.  Casting
  `g_geom.softc` to a copied private `struct md_s` is an undocumented,
  class-specific dependency and does not cover other filesystem-private or
  GEOM-private file-backed providers.

Consequently a visible-only oracle can approve all vnode, credential, pager,
resize, and unmount checks while missing the hidden backing-vnode ancestor.  The
gate includes that case as an adversarial false-GO control.  There is no sound
place to return the required single cycle errno (`EDEADLK`) because the cycle
identity cannot first be discovered.

## Result

`P15-032` is **STOP** and B39 is **STOP-NO-SOURCE**.  Recursive I/O and its lock
graph cannot be statically excluded for the requested regular-file source
surface using documented generic FreeBSD interfaces.  A safe future GO needs a
new kernel dependency API that returns and pins transitive backing vnode
identities, or an explicitly narrower feature contract whose permitted source
filesystems have no hidden storage dependencies.  Neither change is in B39's
authorized write set.

Run from the repository root:

```sh
repo-pre-15/tests/pre15/gates/P15-032.sh \
  --base bd5a09054e5cf89efd4db82aadb051f20b06ebf7 \
  --freebsd-src /work/build/freebsd-src \
  --output /absolute/owned/output/path
```

No EROFS source, Makefile, feature documentation, B39 case, or B39 fixture is
modified by this STOP addendum.  D, H, K, Q, TC006, TC179, TC184, smoke, and the
full feature suite are not run at the gate stage.
