commit cd1c5dbea3a0baa6c23f762bad774bdbc17ee207
parent 0a2639b3dc1794f75c2ca458ef225da1e4f792ea
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Thu, 1 Oct 2026 22:10:35 -0400
common: the index probes the media drive through the watchdog and reads a tiered raw live chat directly; a track neither read nor kept is retried (re-review R1)
One stat under the watchdog's budget, then the read outside it, so a large
replay on a slow platter is neither refused nor marks the location stalled;
when nothing was read or kept the record stores subsMs null and the next
build retries. Case (l), mutation-checked.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
2 files changed, 47 insertions(+), 6 deletions(-)
diff --git a/common/controller/buildIndex.test.ts b/common/controller/buildIndex.test.ts
@@ -769,6 +769,38 @@ test("(k) release 17: a STALLED media drive with a tiered live chat holds neithe
}
});
+test("(l) release 17: a tiered raw live chat that cannot be read, with no cues to keep, is retried by the next build", async () => {
+ resetCorpus();
+ seedVideo("local");
+ seedDriveChannel();
+ const chat =
+ JSON.stringify({ replayChatItemAction: { actions: [{ addChatItemAction: { item: { liveChatTextMessageRenderer: { message: { runs: [{ text: "hi" }] }, authorName: { simpleText: "a" }, timestampUsec: "1000000" } } } }], videoOffsetTimeMsec: "1000" } }) + "\n";
+ mkdirSync(path.join(DRIVE_MEDIA(), "d2"), { recursive: true });
+ writeFileSync(path.join(DRIVE_MEDIA(), "d2", "transcript.live_chat.json"), chat);
+ symlinkSync("../../media/d2/transcript.live_chat.json", path.join(videoDir("d2", DRIVE_CHANNEL), "transcript.live_chat.json"));
+ const tracksOfD2 = () =>
+ withIndex((db) =>
+ [...db("subs").getRange()]
+ .filter(({ key }) => (key as unknown as string[])[2] === "d2")
+ .flatMap(({ value }) => (value as { track: string }[]).map((t) => t.track)),
+ );
+ // The media drive is away for the first build: nothing to read, nothing kept.
+ unmountMedia();
+ try {
+ const away = await runIndex();
+ assert.deepEqual(away.res.heldChannels, []);
+ assert.equal(tracksOfD2().includes("live_chat"), false);
+ } finally {
+ remountMedia();
+ }
+ // Back: nothing on disk moved, yet the record is retried and the chat read.
+ const back = await runIndex();
+ assert.equal(back.res.changed, 1, "d2 is retried");
+ assert.ok(tracksOfD2().includes("live_chat"));
+ // And then it settles.
+ assert.equal((await runIndex()).res.changed, 0);
+});
+
test("(z) no write this file caused landed outside its temp root", () => {
// LMDB writes natively, past the spy: its file must be under the root too.
assert.ok(paths.lmdbPath.startsWith(ROOT + path.sep), paths.lmdbPath);
diff --git a/common/controller/buildIndex.ts b/common/controller/buildIndex.ts
@@ -896,6 +896,9 @@ export async function buildIndex({
else cues.remove(indexKey);
const parsedSubs: StoredSubs = [];
+ // A tiered track that could be neither read nor kept: `subsMs` is
+ // stored null so the next build's scan sees a change and retries.
+ let retrySubs = false;
for (const t of s.subTracks) {
try {
let trackCues: Cue[] | null = null;
@@ -912,9 +915,14 @@ export async function buildIndex({
const rawPath = path.join(path.dirname(s.metaPath), t.filename);
// THE RAW REPLAY IS MEDIA (release 17). Tiered — a link into
// channels/<slug>/media — it is read only while the channel's
- // media is reachable, through the watchdog; otherwise (or when
- // the drive does not answer) the cues the last build held are
- // kept, and the track is re-read on a later build.
+ // media is reachable: the drive is PROBED through the watchdog
+ // (one stat, which is what its budget is sized for), then the
+ // file is read directly — a raw replay of hundreds of MB on a
+ // slow platter would outlast the budget, be refused, and, where
+ // the disk's counters are unknown, mark the location stalled.
+ // Not read (unreachable, not answering, unreadable): the cues
+ // the last build held are kept; with none to keep, the record
+ // is written with no sub-track time, so the next build retries.
const tiered =
t.track === "live_chat" &&
(await lstat(rawPath).then((l) => l.isSymbolicLink(), () => false));
@@ -923,8 +931,8 @@ export async function buildIndex({
let raw: string | null = null;
if (access?.readable) {
try {
- const read = () => readFile(rawPath, "utf8");
- raw = await (access.drive ? onDrive(access.drive, read) : read());
+ if (access.drive) await onDrive(access.drive, () => stat(rawPath));
+ raw = await readFile(rawPath, "utf8");
} catch {
raw = null;
}
@@ -932,6 +940,7 @@ export async function buildIndex({
if (raw === null) {
const kept = prevSubs?.find((x) => x.track === t.track);
if (kept) parsedSubs.push(kept);
+ else retrySubs = true;
continue;
}
trackCues = parseLiveChat(raw);
@@ -1041,7 +1050,7 @@ export async function buildIndex({
mtimes.put(pk, {
metaMs: s.metaMs,
transcriptMs: s.transcriptMs,
- subsMs: s.subsMs,
+ subsMs: retrySubs ? null : s.subsMs,
availabilityMs: s.availabilityMs,
digestMs: s.digestMs,
...(availability ? { availability } : {}),