commit 904dc0df2ee3ba34c884edebb5e88206e8049d32
parent 807a793aa10a01ce4031feb77042b5fcfc0f765b
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Mon, 21 Sep 2026 03:22:20 -0400
review: the free-space guard was asking the wrong two directories
**The HIGH one, and it broke the only location on this machine.**
`volumeFreeBytes` compared `stat(root).dev` with its parent's. A root is
`join(mountpoint, relPath)` — production's `platter` is
`/run/media/user/<uuid>/archilyzer-media`, a SUBDIRECTORY of the udisks
mountpoint — so root and parent are on the same filesystem BY CONSTRUCTION
whenever `relPath` is non-empty, and /channels reported "free space unknown"
for a correctly mounted drive. A bind mount reads the same way.
The boundary is tested at `volume.mountpoint` against its own parent now. Two
syscalls, because the header's rule (TABLES NEVER PROBE) rules out reusing
`probeLocationMemo`, which is up to three subprocesses per location. `/` is
skipped: it is its own parent, and the process is reading from it. Verified
against the real settings.json: platter reports 1.34 TB free.
The test had to FIND a real mount (/run, /dev, /sys, /proc) and point a root at
a plain subdirectory of it — a unit test cannot mount anything, and a tmpdir
has no boundary in it at all, which is exactly the shape that made the broken
version look correct. Two negative cases beside it: a mountpoint that is only a
directory, and one that is missing.
**The destructive evict is gated.** It fired on one click, and the age list
includes "any age (everything)". The button is disabled until a dry run has
been done in this session — the preview is the same walk, so the number the
operator unlocks it with is the one the real pass would produce — and "any age"
needs a checkbox as well. Both reset when the age changes, so a preview of
"older than 90 days" can never arm a sweep that takes everything.
**A NaN age would have evicted EVERYTHING, not nothing.** `Math.max(0, NaN)` is
NaN, so the cutoff is NaN and `st.mtimeMs > NaN` is false for every file. The
controller is exported, so it refuses rather than clamps.
**`parseClipWindowName` reads `.mkv` and `.webm` too.** `clipWindowFile` writes
only `.mp4`, but a window left by an older build — or by a hand-run yt-dlp that
ignored the format pin — was invisible to `listClipWindows` AND to
`evictClipWindows`: bytes nothing counted and nothing could remove. `.json` is
deliberately not in the list; a sidecar must never parse as a window.
Noted in FACTS rather than built: per-channel eviction is reachable only
through `/api/ops/evict-clips` — the card is corpus-wide, which is the shape
the bytes have.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
9 files changed, 347 insertions(+), 21 deletions(-)
diff --git a/common/controller/evictClipWindows.test.ts b/common/controller/evictClipWindows.test.ts
@@ -218,3 +218,23 @@ test("an unmounted drive is a SKIP, never a clean eviction of nothing", async ()
assert.match(r.skipped[0], /^alpha: media unreachable/);
});
});
+
+test("a non-finite age is REFUSED, because clamping it would evict everything", async () => {
+ // `Math.max(0, NaN)` is NaN, `st.mtimeMs > NaN` is false for every file — so
+ // the silent failure mode of a bad number was not "evict nothing" but "evict
+ // the whole corpus". The action validates too; this is the guard on the
+ // exported function.
+ await withTmp(async (paths) => {
+ const clipsDir = await seed(paths, "alpha", {
+ "10.00-40.00.mp4": { ageDays: 0, size: 1000 },
+ });
+ for (const bad of [Number.NaN, Number.POSITIVE_INFINITY, -1]) {
+ await assert.rejects(
+ () => evictClipWindows({ paths, olderThanDays: bad }),
+ /finite number of days/,
+ );
+ }
+ // Nothing was touched on the way to any of those refusals.
+ assert.equal(await exists(path.join(clipsDir, "10.00-40.00.mp4")), true);
+ });
+});
diff --git a/common/controller/evictClipWindows.ts b/common/controller/evictClipWindows.ts
@@ -185,7 +185,18 @@ export async function evictClipWindows(
keptBytes: 0,
skipped: [],
};
- const days = Math.max(0, opts.olderThanDays);
+ // REFUSED, NOT CLAMPED, and NaN is why. `Math.max(0, NaN)` is NaN, the cutoff
+ // is then NaN, and `st.mtimeMs > NaN` is false for every file — so a bad
+ // number would not have evicted nothing, it would have evicted EVERYTHING.
+ // The action validates too, but this function is exported and the failure
+ // mode is silent and total.
+ if (!Number.isFinite(opts.olderThanDays) || opts.olderThanDays < 0) {
+ throw new Error(
+ `olderThanDays must be a finite number of days, zero or more ` +
+ `(got ${String(opts.olderThanDays)})`,
+ );
+ }
+ const days = opts.olderThanDays;
const cutoffMs = Date.now() - days * DAY_MS;
let slugs: string[];
diff --git a/common/controller/storageLocations.test.ts b/common/controller/storageLocations.test.ts
@@ -15,9 +15,11 @@ import type { Paths } from "../lib/paths";
import type { SiteSettings } from "../lib/settings";
import { defaultStorage, sanitizeStorage } from "../lib/settings";
import type { StorageLocation } from "../lib/storageLocations";
+import { readdir, stat } from "node:fs/promises";
import {
channelsOnLocation,
preflightRepoint,
+ volumeFreeBytes,
probeLocationMemo,
recordProbedIdentity,
repointStorageLocation,
@@ -551,3 +553,132 @@ test("the probe memo answers twice from one probe, and refresh bypasses it", asy
test("defaultStorage is still the empty list (the harness's assumption)", () => {
assert.deepEqual(defaultStorage(), { locations: [], defaultLocationId: "" });
});
+
+// ---------------------------------------------------------------------------
+// volumeFreeBytes: the mount boundary is at the MOUNTPOINT, not at the root
+// ---------------------------------------------------------------------------
+//
+// The first version of this guard compared `stat(root).dev` with its parent's,
+// and that is wrong for the only location on the production machine: a root is
+// `join(mountpoint, relPath)`, `platter` is
+// `/run/media/user/<uuid>/archilyzer-media`, and a subdirectory is on the same
+// filesystem as its parent BY CONSTRUCTION. /channels reported "free space
+// unknown" for a correctly mounted drive. A bind mount reads the same way.
+
+function volumeLoc(
+ root: string,
+ volume?: { uuid: string; mountpoint: string; relPath: string },
+): StorageLocation {
+ return {
+ id: "platter",
+ label: "Platter",
+ root,
+ autoRepoint: false,
+ ...(volume ? { volume: { ...volume, fstype: "ext4" } } : {}),
+ };
+}
+
+// A REAL MOUNT BOUNDARY, found rather than made: a unit test cannot mount
+// anything, and a tmpdir has no boundary in it at all — which is exactly the
+// shape that made the broken version look correct. /run, /dev and /sys are
+// separate filesystems on any systemd machine; this takes the first one that
+// actually crosses a boundary and holds a subdirectory to point a root at.
+async function realMountWithSubdir(): Promise<
+ { mount: string; sub: string } | null
+> {
+ for (const mount of ["/run", "/dev", "/sys", "/proc"]) {
+ const [here, above] = await Promise.all([
+ stat(mount).catch(() => null),
+ stat(path.dirname(mount)).catch(() => null),
+ ]);
+ if (!here || !above || here.dev === above.dev) continue;
+ const names = await readdir(mount).catch(() => [] as string[]);
+ for (const name of names) {
+ const full = path.join(mount, name);
+ const st = await stat(full).catch(() => null);
+ // Same filesystem as the mountpoint — a subdirectory of it, not another
+ // mount nested inside.
+ if (st?.isDirectory() && st.dev === here.dev) return { mount, sub: full };
+ }
+ }
+ return null;
+}
+
+test("a root INSIDE a mounted volume reports its free space", async (t) => {
+ const found = await realMountWithSubdir();
+ if (!found) {
+ t.skip("no mounted filesystem with a plain subdirectory on this machine");
+ return;
+ }
+ const paths = { channelsDir: process.cwd() } as Paths;
+ const out = await volumeFreeBytes({
+ paths,
+ locations: [
+ volumeLoc(found.sub, {
+ uuid: "11111111-2222-3333-4444-555555555555",
+ mountpoint: found.mount,
+ relPath: path.basename(found.sub),
+ }),
+ ],
+ });
+ assert.equal(
+ typeof out.platter,
+ "number",
+ `expected free space for ${found.sub} under mountpoint ${found.mount}`,
+ );
+});
+
+test("a mountpoint that is not a mount reports nothing, however real the directory", async () => {
+ const dir = await mkdtemp(path.join(tmpdir(), "ttb-freebytes-"));
+ try {
+ // The shape of an unplugged platter: the mountpoint directory exists, the
+ // root under it exists, and both are on the parent filesystem.
+ const mount = path.join(dir, "media", "platter");
+ const root = path.join(mount, "archilyzer-media");
+ await mkdir(root, { recursive: true });
+ const paths = { channelsDir: dir } as Paths;
+ const out = await volumeFreeBytes({
+ paths,
+ locations: [
+ volumeLoc(root, {
+ uuid: "11111111-2222-3333-4444-555555555555",
+ mountpoint: mount,
+ relPath: "archilyzer-media",
+ }),
+ ],
+ });
+ assert.equal(out.platter, undefined);
+
+ // WITHOUT A LEARNED IDENTITY the same layout is reported, because a
+ // location on a plain directory shares its parent's device legitimately.
+ const plain = await volumeFreeBytes({
+ paths,
+ locations: [volumeLoc(root)],
+ });
+ assert.equal(typeof plain.platter, "number");
+ } finally {
+ await rm(dir, { recursive: true, force: true });
+ }
+});
+
+test("a missing mountpoint reports nothing even when the root somehow exists", async () => {
+ const dir = await mkdtemp(path.join(tmpdir(), "ttb-freebytes-"));
+ try {
+ const mount = path.join(dir, "gone");
+ const root = path.join(dir, "elsewhere", "archilyzer-media");
+ await mkdir(root, { recursive: true });
+ const out = await volumeFreeBytes({
+ paths: { channelsDir: dir } as Paths,
+ locations: [
+ volumeLoc(root, {
+ uuid: "11111111-2222-3333-4444-555555555555",
+ mountpoint: mount,
+ relPath: "archilyzer-media",
+ }),
+ ],
+ });
+ assert.equal(out.platter, undefined);
+ } finally {
+ await rm(dir, { recursive: true, force: true });
+ }
+});
diff --git a/common/controller/storageLocations.ts b/common/controller/storageLocations.ts
@@ -259,16 +259,38 @@ export async function volumeFreeBytes(opts: {
// the disk the operator is trying to empty. The location would then read
// "233 GB free" about a platter that is not plugged in.
//
- // Comparing `st.dev` with the PARENT's is what a mount is: crossing a
- // mount boundary changes the device number. Only asked for a location
- // that has learned a `volume.uuid` — that field is the assertion that
- // this root is supposed to be its own volume. A location on a plain
- // directory (no identity ever probed, a container, a subdirectory of the
- // system disk by design) shares its parent's device legitimately, and
- // refusing to report its free space would be wrong.
- if (loc.volume?.uuid) {
- const parent = await stat(path.dirname(loc.root)).catch(() => null);
- if (parent && parent.dev === st.dev) {
+ // THE BOUNDARY IS TESTED AT THE MOUNTPOINT, NEVER AT THE ROOT, and the
+ // first version of this got that wrong in the one way that matters on
+ // this machine. A location's root is `join(mountpoint, relPath)` — the
+ // production `platter` is `/run/media/user/<uuid>/archilyzer-media`, a
+ // SUBDIRECTORY of the mountpoint — so the root and its parent are on the
+ // same filesystem BY CONSTRUCTION whenever `relPath` is non-empty, and
+ // comparing those two devices reported "free space unknown" for a
+ // correctly mounted drive. A bind mount reads the same way.
+ //
+ // Crossing a mount changes the device number, so `stat(mountpoint).dev`
+ // against `stat(dirname(mountpoint)).dev` is the honest question, and it
+ // is two syscalls — TABLES NEVER PROBE (see the header) rules out asking
+ // `probeLocation`, which is up to three subprocesses.
+ //
+ // Only asked of a location that has learned a `volume.uuid`: that field
+ // is the assertion that the root is supposed to be on its own volume. A
+ // location on a plain directory (never probed, a container, a
+ // subdirectory of the system disk by design) shares its parent's device
+ // legitimately, and withholding its free space would be wrong.
+ const mountpoint = loc.volume?.uuid
+ ? (loc.volume.mountpoint ?? "").trim()
+ : "";
+ // `/` is its own parent, so a volume mounted at the root has no boundary
+ // to test and is trivially there — the process is reading from it.
+ if (mountpoint && mountpoint !== path.dirname(mountpoint)) {
+ const [atMount, aboveMount] = await Promise.all([
+ stat(mountpoint).catch(() => null),
+ stat(path.dirname(mountpoint)).catch(() => null),
+ ]);
+ // Gone entirely, or present as an ordinary directory on the parent
+ // filesystem: either way nothing is mounted there.
+ if (!atMount || (aboveMount && aboveMount.dev === atMount.dev)) {
out[loc.id] = undefined;
return;
}
diff --git a/common/lib/clipWindow.test.ts b/common/lib/clipWindow.test.ts
@@ -20,6 +20,14 @@ test("a window name round-trips through two decimals", () => {
// name can hold still addresses ONE file.
assert.equal(clipWindowName(12.004, 41.999), "12.00-42.00");
assert.deepEqual(parseClipWindowName("12.00-42.00.mp4"), { from: 12, to: 42 });
+ // A window an older build (or a hand-run yt-dlp that ignored the format pin)
+ // left behind. Unreadable names were bytes nothing counted and nothing could
+ // evict, which is the worse half of being invisible.
+ assert.deepEqual(parseClipWindowName("12.00-42.00.mkv"), { from: 12, to: 42 });
+ assert.deepEqual(parseClipWindowName("12.00-42.00.webm"), { from: 12, to: 42 });
+ // NOT a sidecar: it is removed with the window it describes and must never
+ // parse as one.
+ assert.equal(parseClipWindowName("12.00-42.00.json"), null);
assert.deepEqual(parseClipWindowName("12.00-42.00"), { from: 12, to: 42 });
assert.deepEqual(parseClipWindowName("0.00-1234.50.mp4"), {
from: 0,
diff --git a/common/lib/clipWindow.ts b/common/lib/clipWindow.ts
@@ -75,10 +75,29 @@ const WINDOW_RE = /^(\d+(?:\.\d+)?)-(\d+(?:\.\d+)?)$/;
// Parse `<from>-<to>.mp4` (or the bare `<from>-<to>`) back into its numbers, or
// null for anything else in the directory — a sidecar, a `.part`, a stray.
+// THE EXTENSIONS A WINDOW MAY WEAR. `clipWindowFile` writes `.mp4` and only
+// `.mp4` (the format is pinned to H.264 so both sides address the same file —
+// see the header), so the other two are not speculation about a future format:
+// they are what a window fetched by an older build, or by a hand-run yt-dlp
+// that ignored the pin, is called. A parser that could not read those names
+// made those files invisible to `listClipWindows` AND to `evictClipWindows`,
+// which is the worse half — bytes nothing counts and nothing can remove.
+//
+// NOT `.json`: a sidecar is removed with the window it describes and must
+// never parse as one itself.
+const CLIP_EXTS = [".mp4", ".mkv", ".webm"] as const;
+
+function stripClipExt(name: string): string {
+ for (const ext of CLIP_EXTS) {
+ if (name.endsWith(ext)) return name.slice(0, -ext.length);
+ }
+ return name;
+}
+
export function parseClipWindowName(
name: string,
): { from: number; to: number } | null {
- const stem = name.endsWith(".mp4") ? name.slice(0, -4) : name;
+ const stem = stripClipExt(name);
const m = WINDOW_RE.exec(stem);
if (!m) return null;
const from = Number(m[1]);
diff --git a/editor/app/storage/components/ClipWindowsCard.tsx b/editor/app/storage/components/ClipWindowsCard.tsx
@@ -27,9 +27,19 @@ import { evictClipWindowsAction } from "../actions";
// "older than N days", and an operator evicting something a report still cites
// has spent a fetch, not lost data. They are entitled to know that beforehand.
//
-// PREVIEW FIRST, and the preview is the same walk. `dryRun` runs the identical
-// pass and deletes nothing, so the number in the log is produced by the code
-// that would do the work rather than by a second estimate that can disagree.
+// PREVIEW FIRST, AND THE PREVIEW IS THE GATE. `dryRun` runs the identical pass
+// and deletes nothing, so the number in the log is produced by the code that
+// would do the work rather than by a second estimate that can disagree — and
+// until one has run in this session the destructive button is disabled. One
+// click should not be able to delete every clip window in the corpus, and
+// "older than 30 days" over a corpus nobody has looked at is a number the
+// operator has no way to picture.
+//
+// "ANY AGE" IS A SECOND GATE. `0` means every window on every drive, which is
+// a legitimate ask (the operator is emptying a disk) and the one setting where
+// a preview alone is not enough of a pause. It needs the checkbox ticked as
+// well, every time — the tick resets when the age changes, so it can never be
+// left armed from an earlier, narrower choice.
//
// ⚠️ Both panels are rendered UNCONDITIONALLY and only `disabled` changes —
// `StreamActionLog` calls router.refresh() the instant a run ends, and a panel
@@ -39,6 +49,13 @@ const AGES = [0, 7, 30, 90, 180] as const;
export function ClipWindowsCard({ clipsBytes }: { clipsBytes: number }) {
const [days, setDays] = useState<number>(30);
+ // Reset by any change of age: a preview of "older than 90 days" says nothing
+ // about what "any age" would take, and an armed checkbox from a narrower
+ // choice is exactly the thing this gate exists to stop.
+ const [previewed, setPreviewed] = useState(false);
+ const [confirmed, setConfirmed] = useState(false);
+ const takesEverything = days === 0;
+ const canEvict = previewed && (!takesEverything || confirmed);
return (
<article
aria-label="clip windows"
@@ -78,7 +95,11 @@ export function ClipWindowsCard({ clipsBytes }: { clipsBytes: number }) {
<select
aria-label="clip eviction age"
value={days}
- onChange={(e) => setDays(Number(e.target.value))}
+ onChange={(e) => {
+ setDays(Number(e.target.value));
+ setPreviewed(false);
+ setConfirmed(false);
+ }}
className="rounded border border-border bg-background px-2 py-1 text-sm"
>
{AGES.map((d) => (
@@ -89,12 +110,29 @@ export function ClipWindowsCard({ clipsBytes }: { clipsBytes: number }) {
</select>
</label>
+ {takesEverything && (
+ <label className="flex items-start gap-2 text-sm">
+ <input
+ type="checkbox"
+ aria-label="confirm evicting every window"
+ checked={confirmed}
+ onChange={(e) => setConfirmed(e.target.checked)}
+ className="mt-1"
+ />
+ <span>
+ Yes, evict <strong>every</strong> fetched window on every drive,
+ however recently it was fetched.
+ </span>
+ </label>
+ )}
+
<div className="flex flex-wrap items-start gap-4">
<StreamActionLog
key="evict-clips-preview-log"
- trigger={() =>
- evictClipWindowsAction({ olderThanDays: days, dryRun: true })
- }
+ trigger={() => {
+ setPreviewed(true);
+ return evictClipWindowsAction({ olderThanDays: days, dryRun: true });
+ }}
cancelAction={cancelJobAction}
buttonLabel="Preview eviction"
runningLabel="Walking…"
@@ -107,8 +145,16 @@ export function ClipWindowsCard({ clipsBytes }: { clipsBytes: number }) {
buttonLabel="Evict fetched windows"
runningLabel="Evicting…"
label="Evict fetched windows"
+ disabled={!canEvict}
/>
</div>
+ {!canEvict && (
+ <p aria-label="clip eviction gate" className="text-xs text-muted-foreground">
+ {previewed
+ ? "Tick the box above to evict every window."
+ : "Preview first — the eviction button unlocks once you have seen what it would take."}
+ </p>
+ )}
</article>
);
}
diff --git a/editor/e2e/storage-locations.spec.ts b/editor/e2e/storage-locations.spec.ts
@@ -604,8 +604,19 @@ test("Evict fetched windows removes an old one and leaves a recent one", async (
"by age only",
);
- // --- preview deletes nothing -------------------------------------------
+ // --- the destructive button is GATED until a preview has run ------------
+ // One click must not be able to delete every window in the corpus, and
+ // "older than 30 days" over a corpus nobody has looked at is a number the
+ // operator cannot picture.
await page.getByLabel("clip eviction age").selectOption("30");
+ await expect(
+ page.getByRole("button", { name: "Evict fetched windows" }),
+ ).toBeDisabled();
+ await expect(page.getByLabel("clip eviction gate")).toContainText(
+ "Preview first",
+ );
+
+ // --- preview deletes nothing -------------------------------------------
await page.getByRole("button", { name: "Preview eviction" }).click();
await expect(page.getByLabel("Preview eviction output")).toContainText(
/Would evict 1 window/,
@@ -614,7 +625,9 @@ test("Evict fetched windows removes an old one and leaves a recent one", async (
expect(await pathExists(old)).toBe(true);
// --- and then it does ---------------------------------------------------
- await page.getByRole("button", { name: "Evict fetched windows" }).click();
+ const evict = page.getByRole("button", { name: "Evict fetched windows" });
+ await expect(evict).toBeEnabled();
+ await evict.click();
await expect(page.getByLabel("Evict fetched windows output")).toContainText(
/Evicted 1 window/,
{ timeout: 60_000 },
@@ -625,3 +638,39 @@ test("Evict fetched windows removes an old one and leaves a recent one", async (
expect(await pathExists(recent)).toBe(true);
expect(await pathExists(recent.replace(/\.mp4$/, ".json"))).toBe(true);
});
+
+// "ANY AGE" IS EVERY WINDOW ON EVERY DRIVE. A preview is enough of a pause for
+// a dated sweep; it is not enough for that, so the tick is required as well —
+// and it resets when the age changes, so it can never be left armed from an
+// earlier, narrower choice.
+test("evicting at any age needs the tick as well as the preview", async ({
+ page,
+}) => {
+ test.setTimeout(90_000);
+ await resetData("one-youtube-channel-with-data");
+ await writeSettings({ adminTitle: "Test Admin", minFreeDiskGB: 0 });
+
+ await page.goto("/storage");
+ const evict = page.getByRole("button", { name: "Evict fetched windows" });
+ await page.getByLabel("clip eviction age").selectOption("0");
+ const confirm = page.getByLabel("confirm evicting every window");
+ await expect(confirm).toBeVisible();
+
+ // Ticked but never previewed: still refused.
+ await confirm.check();
+ await expect(evict).toBeDisabled();
+
+ await page.getByRole("button", { name: "Preview eviction" }).click();
+ await expect(page.getByLabel("Preview eviction output")).toContainText(
+ /Would evict/,
+ { timeout: 60_000 },
+ );
+ await expect(evict).toBeEnabled();
+
+ // CHANGING THE AGE DISARMS BOTH. A preview of "any age" says nothing about
+ // what a narrower sweep would take, and vice versa.
+ await page.getByLabel("clip eviction age").selectOption("90");
+ await expect(evict).toBeDisabled();
+ await page.getByLabel("clip eviction age").selectOption("0");
+ await expect(page.getByLabel("confirm evicting every window")).not.toBeChecked();
+});
diff --git a/plans/FACTS.md b/plans/FACTS.md
@@ -4735,10 +4735,30 @@ or an explicit clips total, a count on the storage page, and an eviction rule").
there is no slug for `runManagedFunction` to check. Without it the pass
reports a clean eviction of zero bytes about a platter full of windows.
`in-transition` is refused outright: the mover's verify pass compares trees.
+- **Per-channel eviction has no UI.** `evictClipWindows` takes a `slug` and the
+ job sets `channelSlug` (which is what makes the snapshot regen land), but the
+ only caller that passes one is `POST /api/ops/evict-clips`. The /storage card
+ is corpus-wide, which is the shape the bytes have — a channel page button is
+ the obvious next surface and nothing blocks it.
- The empty `clips/` is left behind on purpose — `rmdir` would race a fetch that
just created it, and an empty directory is invisible to every video-dir
enumerator anyway.
+### `volumeFreeBytes` tests the mount boundary AT THE MOUNTPOINT
+
+- **Comparing `stat(root).dev` with its parent's is wrong**, and it broke the
+ only location on this machine. A root is `join(mountpoint, relPath)`;
+ `platter` is `/run/media/user/<uuid>/archilyzer-media`, a SUBDIRECTORY of the
+ mountpoint, so root and parent are on the same filesystem by construction and
+ /channels reported "free space unknown" for a correctly mounted drive. A bind
+ mount reads the same way. The question is asked of `volume.mountpoint`
+ against its own parent — two syscalls, because TABLES NEVER PROBE rules out
+ `probeLocation`. Only for a location that has learned a `volume.uuid`.
+- A unit test cannot mount anything and a tmpdir has no boundary in it at all —
+ which is exactly the shape that made the broken version look right. The test
+ FINDS a real one (/run, /dev, /sys, /proc) and points a root at a plain
+ subdirectory of it.
+
### `assertRelocationRootPresent`
- **Every absolute `mkdir` in both movers is `{recursive: true}`**, so a move