commit a1dbe0b13af15e1083f18df55854fab3b7caa587
parent 9849af0a3bb6302a36a36a3dc4d486af3807253e
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Fri, 2 Oct 2026 01:30:55 -0400
common, editor: review L3, N9, N5 — a tier migration's marker is never cleared or previewed past; identity before tiering
tierMigrationRefusal is exported and gives one sentence to the job, the
preview (which now refuses too and tiers nothing), Resume/Reconcile and Clear
marker (which no longer removes the phase a migration resumes from). The
move-out checks the destination's presence and identity before the preflight
tiers the channel.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
3 files changed, 32 insertions(+), 12 deletions(-)
diff --git a/common/controller/relocateChannelMedia.test.ts b/common/controller/relocateChannelMedia.test.ts
@@ -663,6 +663,15 @@ test("a tier migration's marker is never resumed or replaced by the mover", asyn
/media-tier migration in flight or interrupted .* archilyzer storage migrate-tier alpha/,
);
}
+ // The preview gives the same sentence, and tiers nothing (review N9).
+ await assert.rejects(
+ () => previewRelocation({ paths, slug: "alpha", root }),
+ /media-tier migration in flight or interrupted .* archilyzer storage migrate-tier alpha/,
+ );
+ assert.equal(
+ await pathIsThere(path.join(paths.channelsDir, "alpha", "media")),
+ false,
+ );
assert.equal((await readRelocationMarker(paths, "alpha"))?.scope, "tier-migration");
});
});
diff --git a/common/controller/relocateChannelMedia.ts b/common/controller/relocateChannelMedia.ts
@@ -227,9 +227,11 @@ async function legacyRefusal(
return `Channel "${slug}" cannot be moved: ${legacyDetail(slug)}.`;
}
-// A TIER MIGRATION'S MARKER is not this mover's to resume or replace: the
-// migration rebuilds `data/` itself, with the editor stopped.
-function tierMigrationRefusal(
+// A TIER MIGRATION'S MARKER is not this mover's to resume, replace or clear:
+// the migration rebuilds `data/` itself, with the editor stopped, and resumes
+// from its marker's phase. ONE sentence, for the job, the preview, and the
+// Storage panel's Resume, Reconcile and Clear marker.
+export function tierMigrationRefusal(
slug: string,
marker: RelocationMarker | null,
): string | null {
@@ -589,6 +591,8 @@ export async function previewRelocation({
if (problem) throw new Error(problem);
const channelDir = path.join(paths.channelsDir, slug);
const config = await readChannelConfig(paths, slug);
+ const migrating = tierMigrationRefusal(slug, await readMarkerRaw(paths, slug));
+ if (migrating) throw new Error(migrating);
const legacy = await legacyRefusal(channelDir, slug, config);
if (legacy) throw new Error(legacy);
// The same refusal the job's first step gives, before the operator commits.
@@ -875,6 +879,12 @@ async function moveOut(args: {
// until the next sweep tiers it onto the far side).
let tiered = 0;
if (args.phase === "copy") {
+ // THE DESTINATION'S IDENTITY FIRST (review N5): a bare mountpoint, or a
+ // drive whose learned volume does not match, is refused before the
+ // channel is touched at all — the tiering below is harmless, but "refused
+ // before anything" should be true here too. Asked again immediately before
+ // the mkdir below, for a job that sat in the queue.
+ await assertRelocationRootPresent(root, args.io.read().storage, paths);
const counts = await tierChannelMedia(paths, slug, {
createMediaDir: true,
onLog: (line) => log(line.replace(/\n$/, "")),
diff --git a/editor/app/channels/[slug]/storageActions.ts b/editor/app/channels/[slug]/storageActions.ts
@@ -37,6 +37,7 @@ import { relocatedMediaDir } from "yt-dlp-transcript-common/lib/mediaTier-server
import {
previewRelocation,
relocationRootProblem,
+ tierMigrationRefusal,
type RelocationPreview,
} from "yt-dlp-transcript-common/controller/relocateChannelMedia";
import { enqueueRelocation } from "../lib/relocationJob";
@@ -169,15 +170,8 @@ async function resumeRelocation(
if (refusal) return { ok: false, error: refusal };
// A TIER MIGRATION'S MARKER (release 17) is not this panel's to resume:
// the migration runs with the editor stopped and resumes itself.
- if (marker.scope === "tier-migration") {
- return {
- ok: false,
- error:
- `Channel "${slug}" has a media-tier migration in flight or ` +
- `interrupted (phase "${marker.phase}") — finish it with ` +
- `archilyzer storage migrate-tier ${slug}, with the editor stopped.`,
- };
- }
+ const migrating = tierMigrationRefusal(slug, marker);
+ if (migrating) return { ok: false, error: migrating };
if (marker.direction === "back") {
return enqueueRelocation({ slug, direction: "back", reconcile });
}
@@ -228,6 +222,13 @@ export async function clearRelocationMarkerAction(
{ mediaOnly: true },
);
if (refusal) return { ok: false, error: refusal };
+ // AN INTERRUPTED TIER MIGRATION'S MARKER is never cleared from here: it is
+ // the phase the migration resumes from (the same sentence Resume gives).
+ const migrating = tierMigrationRefusal(
+ slug,
+ await readRelocationMarker(getPaths(), slug),
+ );
+ if (migrating) return { ok: false, error: migrating };
try {
await clearRelocationMarker(getPaths(), slug);
} catch (e) {