# P15-092 Stage0 Decision

Status: `STOP`. B15 is `STOP-NO-SOURCE`; terminal static review status is
`TERMINAL_REVIEWED_NO_SOURCE`. No production source, B15 case, or B15 fixture
was created. Historical acceptance remains `NOT_TESTED`.

P15-092 is the G11 zero-nlink candidate. The frozen BASE is
`b22dae8dc634c68db4ea89dccade91350614c139`, the FreeBSD source HEAD is
`106727738dcfb6c001b46f25363b91cece970085`, the erofs-utils source HEAD is
`7db78788b000999e2de88decd2ba90654f26171c`, and the generator is
`mkfs.erofs (erofs-utils) 1.8.6`. Linux EROFS identity is frozen by the three
source hashes in `P15-092-input.json` and the Pre15 semantic audit hash ledger.

The final gate script and input SHA256 values are
`5943b85864da9095174ef51d1eed032ec6f0a1766c6465cb9660ea20cbd46b1e`
and `0a6f6c76185644d3f5525a98429a310027556ef3cf7ebb43abb912f92e052bde`.

## Real Disk Oracle

The gate creates the same deterministic source tree twice in forced compact
form and twice in forced extended form. It uses fixed UUID, timestamp, worker
count, xattr policy, and inline-data policy. Both complete host output trees
are byte-identical.

An independent parser reads the real superblock, checksum span, metadata block,
root NID, directory entries, compact `i_nb.nlink`, extended `i_nlink`, and the
compact `EROFS_I_NLINK_1_BIT` rule. It then materializes seven images:

- compact and extended positive seeds;
- namespace-reachable compact, extended, and directory inodes with nlink zero;
- a root inode with nlink zero;
- an unreachable inode with nlink zero, whose former name is redirected to a
  real hardlink NID and whose replacement link count is corrected to three.

Every derived image has a recomputed valid EROFS CRC32C. `dump.erofs --path`
resolves the actual directory edge and NID; no DUT helper or internal value
injection is used. All seven images, including all three namespace-reachable
zero-nlink images, pass `fsck.erofs -d0` with no error marker. The fixture-set
SHA256 is `99897d4fd81da015bdaa66a5dfdbf3f42e9e830882c0d8c979e691fa9209ee7a`.

Key deterministic host evidence SHA256 values are:

- `host-result.json`: `59a27e9389e2771c921d689f6b76caeb858f0fd28487a933529153727aac12e8`
- `disk-records.json`: `44bf0355dc5b34e93ec6868db9f9049de32500591b048a9016bf46aed9609485`
- `boundary.json`: `985df1236c6dd2016d6cb244b71578d36bd4cd10aa273b60a92d5bb6d24d5b60`
- `format-semantics.json`: `88efc1d7814103c044b36ff4dc3cac1dfa4da79f3d8955c682d1a60d9bfd9fcf`
- `fixture-index.json`: `4d2b69bcc31709ecb96cfafb41f5db8ffcc97215c3290cfa476d0b6c7fc27886`
- `source-anchors.json`: `ef9e6871d5fe1a3a6113f1172340269da4ea0c1692003aad0b0fee06ec4deb42`

## Linux and FreeBSD Semantics

Linux EROFS calls `set_nlink()` with the compact or extended disk value and has
no zero-nlink rejection in inode decode or super initialization. This is an
inode/link-count behavior, not a FreeBSD vnode publication rule. Linux and
FreeBSD both permit a live unlinked inode/vnode with link count zero; the
FreeBSD VM code explicitly treats `va_nlink == 0` as an unlinked mapping case.

The FreeBSD EROFS adapter has three distinct entry semantics:

1. `erofs_lookup()` owns a non-dot namespace edge and calls `erofs_vget()`.
2. `.vfs_vget = erofs_vget` is the raw NID entry used by VFS/root callers.
3. `erofs_fhtovp()` calls raw `VFS_VGET` and then maps `nlink == 0` to `ESTALE`.

Therefore Linux's direct `set_nlink()` behavior does not authorize either a
global FreeBSD rejection or a namespace-only rejection. The latter would be a
new FreeBSD validator policy, and the disk/fsck corpus supplies no format-level
rule requiring it.

## Publication Boundary

B09 moved the vnode constructor to `src/erofs_vnops.c`, but the frozen B15
write set is only `src/namei.c` and `src/inode.c`. `erofs_vget()` inserts the
constructing vnode in the hash, decodes the inode, marks it
`VSTATE_CONSTRUCTED`, and only then returns to `erofs_lookup()`.

A post-`erofs_vget()` check in `namei.c` is therefore too late to reject before
vnode publication. `vput()` alone does not perform the required `vgone`
cleanup. An unconditional `vgone()` cannot preserve an already cached vnode
obtained through raw `VFS_VGET`, because `erofs_vget()` does not return a
created-versus-hit indicator. Pre-reading through `erofs_read_inode()` would
duplicate complete inode decode and metadata I/O on every cold namespace
lookup, then decode the same inode again in `erofs_vget()`.

The four failed GO requirements are consequently:

- no format or cross-kernel rule requires reachable zero-nlink rejection;
- rejection before vnode publication is unavailable within the exact B15
  write set;
- post-publication cleanup cannot preserve cached raw VGET semantics;
- pre-reading would duplicate full inode decode for a low-value validator.

Namespace and raw entrypoints are distinguishable, and the orphan needs no
mount-wide scan, but those two facts do not outweigh the failed safety,
compatibility, and cost requirements. P15-092 is therefore STOP rather than a
partial or expanded-write-set implementation.

## Replay and Infrastructure

The authoritative deterministic host command was run twice:

```sh
timeout -k 10 240 tests/pre15/gates/P15-092.sh \
    --base b22dae8dc634c68db4ea89dccade91350614c139 \
    --output OWNED_OUTPUT --host-only
```

Both runs exit `22` with `STOP` and have byte-identical host output trees.

One supplemental isolated runtime attempt used the standalone
`/work/debug-qemu/local/vm/freebsd-build-runtime.qcow2`, not the protected base
bp, with a fresh overlay, random port, exact baseline KLD, 180-second boot
deadline, 60-second guest command deadlines, 900-second inner timeout, and
1200-second outer timeout. Run
`20260815T083854Z-qemu-P15-092-gate-runtime-1219662-0` was
`INFRA_BLOCKED`: guest SSH did not become ready before the boot deadline, so no
runtime vnode result is claimed. The run used only PID `1219981` and port
`49795`; it did not own PID `26318`, port `9222`, or the protected base bp.

Cleanup is `PASS`: the owned QEMU PID stopped, port `49795` is free, the
overlay and case temp were removed, and the standalone base SHA256 remained
`ae09f47aef43cfd016049610e86bcf2073fdf70430961bcdb94662facac9f046`.
The runtime manifest and cleanup SHA256 values are
`2e9a4220dc86c5d8fa465e77e6d81e5e36c0f26338b70d71a84881af8fc02cb9`
and `0a499d8fa3397f73998c6c3c3ae9dfc9070f61267a86361ba96d1e8bc64cb392`.

Because the reproducible host gate already fails mandatory GO conditions, the
runtime infrastructure block does not defer or weaken the STOP decision. It is
reported separately and was not retried with another long-running VM. B15,
TC021, TC025, TC182, K0, and B15 acceptance QEMU are `NOT_RUN` by the mandatory
STOP-NO-SOURCE rule. The full feature suite was not run.

## Terminal Static Review

The 2026-08-17 review rechecked the current `repo-pre-15`, Linux EROFS
implementation, P15-092 plan, frozen write set, and post-STOP history. It
found no format or cross-kernel requirement for namespace zero-nlink
rejection, no new B15 consumer or test target, and no safe implementation
entry within the frozen write set. The detailed evidence is
`planning/pre15/evidence/20260817T-B15-terminal-review/VERDICT.md`.

The terminal review does not convert the historical STOP or acceptance state:
`STOP_NO_SOURCE` and `NOT_TESTED` remain the authoritative historical values.
