From 313608baaa838178bfa19d90ea024b41cdeb4901 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 6 Aug 2026 10:10:10 +0200 Subject: [PATCH 1/5] fix(codex): bound oversized rollout inspection --- src/codex/native-residue.ts | 43 +++++++++++++++++++++--------- tests/codex-native-residue.test.ts | 13 +++++++++ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/codex/native-residue.ts b/src/codex/native-residue.ts index 1f3bd6551f..0beb8b6d9d 100644 --- a/src/codex/native-residue.ts +++ b/src/codex/native-residue.ts @@ -69,6 +69,7 @@ const MODELS_CACHE_FILE_NAME = basename(CODEX_MODELS_CACHE_PATH); const JOURNAL_FILE_NAME = "opencodex-journal.json"; const HISTORY_DATABASE_FILE_NAME = "state_5.sqlite"; const ROUTED_CATALOG_DESCRIPTION_PREFIX = "Routed via opencodex → "; +const MAX_ROLLOUT_INSPECTION_BYTES = 64 * 1024 * 1024; function errorCode(error: unknown): string | undefined { return (error as NodeJS.ErrnoException | undefined)?.code; @@ -358,47 +359,65 @@ function classifyReferencedRollout( surface: "history" | "history-backup", reference: RolloutReference, ): NativeRoutedResidueResult { - const read = readRegularFile(reference.path); - if (read.kind === "absent") { + const resolved = resolveRegularFile(reference.path); + if (resolved.kind === "absent") { return indeterminate(surface, reference.path, "referenced rollout is absent"); } - if (read.kind === "indeterminate") return indeterminate(surface, reference.path, read.reason); + if (resolved.kind === "indeterminate") return indeterminate(surface, reference.path, resolved.reason); + if (resolved.stat.size > MAX_ROLLOUT_INSPECTION_BYTES) { + return indeterminate( + surface, + resolved.path, + `referenced rollout exceeds the ${MAX_ROLLOUT_INSPECTION_BYTES} byte inspection limit`, + ); + } + + let content: string; + try { + content = readFileSync(resolved.path, "utf8"); + const after = statSync(resolved.path); + if (!sameStat(resolved.stat, after)) { + return indeterminate(surface, resolved.path, "rollout changed while it was being observed"); + } + } catch (error) { + return indeterminate(surface, resolved.path, `unreadable rollout: ${errorReason(error)}`); + } let first: Record | undefined; let latest: Record | undefined; - for (const line of read.content.split("\n")) { + + for (const line of content.split("\n")) { if (!line.trim()) continue; let parsed: unknown; try { parsed = JSON.parse(line); } catch (error) { - return indeterminate(surface, read.path, `malformed rollout JSONL: ${errorReason(error)}`); + return indeterminate(surface, resolved.path, `malformed rollout JSONL: ${errorReason(error)}`); } if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { - return indeterminate(surface, read.path, "rollout JSONL record is not an object"); + return indeterminate(surface, resolved.path, "rollout JSONL record is not an object"); } const record = parsed as Record; if (record.type !== "session_meta") continue; if (!record.payload || typeof record.payload !== "object" || Array.isArray(record.payload)) { - return indeterminate(surface, read.path, "session_meta payload has an unknown shape"); + return indeterminate(surface, resolved.path, "session_meta payload has an unknown shape"); } const payload = record.payload as Record; first ??= payload; latest = payload; } - if (!first || !latest) { - return indeterminate(surface, read.path, "referenced rollout has no session_meta metadata"); + return indeterminate(surface, resolved.path, "referenced rollout has no session_meta metadata"); } for (const [position, payload] of [["first", first], ["latest", latest]] as const) { if (payload.id !== reference.id) { - return indeterminate(surface, read.path, `${position} session_meta does not identify the referenced thread`); + return indeterminate(surface, resolved.path, `${position} session_meta does not identify the referenced thread`); } if (typeof payload.model_provider !== "string" || !payload.model_provider) { - return indeterminate(surface, read.path, `${position} session_meta has no provider metadata`); + return indeterminate(surface, resolved.path, `${position} session_meta has no provider metadata`); } if (payload.model_provider === "opencodex") { - return { kind: "residue", surface, path: read.path }; + return { kind: "residue", surface, path: resolved.path }; } } return { kind: "clean" }; diff --git a/tests/codex-native-residue.test.ts b/tests/codex-native-residue.test.ts index b086e3d5f5..66fbe5cb23 100644 --- a/tests/codex-native-residue.test.ts +++ b/tests/codex-native-residue.test.ts @@ -9,6 +9,7 @@ import { realpathSync, rmSync, symlinkSync, + truncateSync, writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; @@ -643,6 +644,18 @@ test("a referenced rollout with native first and latest metadata is clean", () = expect(classifyNativeRoutedResidue()).toEqual({ kind: "clean" }); }); +test("an oversized referenced rollout is indeterminate without being loaded", () => { + createHistoryDatabase("openai"); + truncateSync(pathInCodexHome("rollout.jsonl"), 64 * 1024 * 1024 + 1); + + expect(classifyNativeRoutedResidue()).toMatchObject({ + kind: "indeterminate", + surface: "history", + path: pathInCodexHome("rollout.jsonl"), + reason: expect.stringContaining("inspection limit"), + }); +}); + for (const fixture of [ { name: "missing", From e248660d68228892ea591188affe22feaf3efd10 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 6 Aug 2026 14:08:29 +0200 Subject: [PATCH 2/5] fix(codex): bound rollout inspection reads and validate latest metadata --- src/codex/native-residue.ts | 63 ++++++++++++++++++++++++------ tests/codex-native-residue.test.ts | 15 +++++++ 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/codex/native-residue.ts b/src/codex/native-residue.ts index 0beb8b6d9d..3e5b5a6bc1 100644 --- a/src/codex/native-residue.ts +++ b/src/codex/native-residue.ts @@ -1,5 +1,15 @@ import { createHash } from "node:crypto"; -import { lstatSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs"; +import { + closeSync, + fstatSync, + lstatSync, + openSync, + readFileSync, + readdirSync, + realpathSync, + readSync, + statSync, +} from "node:fs"; import type { Stats } from "node:fs"; import { basename, dirname, join, resolve } from "node:path"; @@ -364,23 +374,49 @@ function classifyReferencedRollout( return indeterminate(surface, reference.path, "referenced rollout is absent"); } if (resolved.kind === "indeterminate") return indeterminate(surface, reference.path, resolved.reason); - if (resolved.stat.size > MAX_ROLLOUT_INSPECTION_BYTES) { - return indeterminate( - surface, - resolved.path, - `referenced rollout exceeds the ${MAX_ROLLOUT_INSPECTION_BYTES} byte inspection limit`, - ); + + let handle: number; + try { + handle = openSync(resolved.path, "r"); + } catch (error) { + return indeterminate(surface, resolved.path, `unreadable rollout: ${errorReason(error)}`); } let content: string; try { - content = readFileSync(resolved.path, "utf8"); - const after = statSync(resolved.path); + const opened = fstatSync(handle); + if (opened.size > MAX_ROLLOUT_INSPECTION_BYTES) { + return indeterminate( + surface, + resolved.path, + `referenced rollout exceeds the ${MAX_ROLLOUT_INSPECTION_BYTES} byte inspection limit`, + ); + } + const buffer = Buffer.allocUnsafe(opened.size); + let offset = 0; + while (offset < buffer.length) { + const read = readSync(handle, buffer, offset, buffer.length - offset, offset); + if (read <= 0) { + return indeterminate(surface, resolved.path, "rollout read ended before the observed size"); + } + offset += read; + } + const after = fstatSync(handle); if (!sameStat(resolved.stat, after)) { return indeterminate(surface, resolved.path, "rollout changed while it was being observed"); } + content = buffer.toString("utf8"); } catch (error) { + if (errorCode(error) === "ENOENT") { + return indeterminate(surface, resolved.path, "referenced rollout is absent"); + } return indeterminate(surface, resolved.path, `unreadable rollout: ${errorReason(error)}`); + } finally { + try { + closeSync(handle); + } catch { + // Closing an already-closed descriptor cannot affect the classification. + } } let first: Record | undefined; @@ -409,6 +445,7 @@ function classifyReferencedRollout( if (!first || !latest) { return indeterminate(surface, resolved.path, "referenced rollout has no session_meta metadata"); } + let hasOpenCodexProvider = false; for (const [position, payload] of [["first", first], ["latest", latest]] as const) { if (payload.id !== reference.id) { return indeterminate(surface, resolved.path, `${position} session_meta does not identify the referenced thread`); @@ -416,11 +453,11 @@ function classifyReferencedRollout( if (typeof payload.model_provider !== "string" || !payload.model_provider) { return indeterminate(surface, resolved.path, `${position} session_meta has no provider metadata`); } - if (payload.model_provider === "opencodex") { - return { kind: "residue", surface, path: resolved.path }; - } + hasOpenCodexProvider = hasOpenCodexProvider || payload.model_provider === "opencodex"; } - return { kind: "clean" }; + return hasOpenCodexProvider + ? { kind: "residue", surface, path: resolved.path } + : { kind: "clean" }; } function classifyReferencedRollouts( diff --git a/tests/codex-native-residue.test.ts b/tests/codex-native-residue.test.ts index 66fbe5cb23..437c3dadbc 100644 --- a/tests/codex-native-residue.test.ts +++ b/tests/codex-native-residue.test.ts @@ -638,6 +638,21 @@ test("routed first rollout metadata is residue even when the latest metadata is }); }); +test("an opencodex first rollout with invalid latest metadata is indeterminate", () => { + createHistoryDatabase("openai"); + writeFileSync( + pathInCodexHome("rollout.jsonl"), + sessionMeta("thread-1", "opencodex") + "\n" + sessionMeta("thread-2", "openai") + "\n", + ); + + expect(classifyNativeRoutedResidue()).toMatchObject({ + kind: "indeterminate", + surface: "history", + path: pathInCodexHome("rollout.jsonl"), + reason: expect.stringContaining("latest session_meta"), + }); +}); + test("a referenced rollout with native first and latest metadata is clean", () => { createHistoryDatabase("openai", ["openai", "openai"]); From 7f8053c64d5aa7649ac3f6cd708b995ce5a9d1b4 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 6 Aug 2026 14:16:01 +0200 Subject: [PATCH 3/5] fix(codex): parse rollout JSONL incrementally from bounded chunks --- src/codex/native-residue.ts | 89 +++++++++++++++++++----------- tests/codex-native-residue.test.ts | 11 ++++ 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/src/codex/native-residue.ts b/src/codex/native-residue.ts index 3e5b5a6bc1..0d3e8c72c1 100644 --- a/src/codex/native-residue.ts +++ b/src/codex/native-residue.ts @@ -80,6 +80,7 @@ const JOURNAL_FILE_NAME = "opencodex-journal.json"; const HISTORY_DATABASE_FILE_NAME = "state_5.sqlite"; const ROUTED_CATALOG_DESCRIPTION_PREFIX = "Routed via opencodex → "; const MAX_ROLLOUT_INSPECTION_BYTES = 64 * 1024 * 1024; +const ROLLOUT_READ_CHUNK_BYTES = 64 * 1024; function errorCode(error: unknown): string | undefined { return (error as NodeJS.ErrnoException | undefined)?.code; @@ -152,6 +153,26 @@ function indeterminate( return { kind: "indeterminate", surface, path, reason }; } +function rolloutSessionMetaPayload( + line: string, +): { kind: "payload"; payload: Record | null } | { kind: "malformed"; reason: string } { + let parsed: unknown; + try { + parsed = JSON.parse(line); + } catch (error) { + return { kind: "malformed", reason: `malformed rollout JSONL: ${errorReason(error)}` }; + } + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + return { kind: "malformed", reason: "rollout JSONL record is not an object" }; + } + const record = parsed as Record; + if (record.type !== "session_meta") return { kind: "payload", payload: null }; + if (!record.payload || typeof record.payload !== "object" || Array.isArray(record.payload)) { + return { kind: "malformed", reason: "session_meta payload has an unknown shape" }; + } + return { kind: "payload", payload: record.payload as Record }; +} + function classifyToml( surface: "config" | "profile", path: string, @@ -382,7 +403,10 @@ function classifyReferencedRollout( return indeterminate(surface, resolved.path, `unreadable rollout: ${errorReason(error)}`); } - let content: string; + let first: Record | undefined; + let latest: Record | undefined; + let partial = ""; + let totalRead = 0; try { const opened = fstatSync(handle); if (opened.size > MAX_ROLLOUT_INSPECTION_BYTES) { @@ -392,20 +416,46 @@ function classifyReferencedRollout( `referenced rollout exceeds the ${MAX_ROLLOUT_INSPECTION_BYTES} byte inspection limit`, ); } - const buffer = Buffer.allocUnsafe(opened.size); - let offset = 0; - while (offset < buffer.length) { - const read = readSync(handle, buffer, offset, buffer.length - offset, offset); - if (read <= 0) { + const buffer = Buffer.allocUnsafe(ROLLOUT_READ_CHUNK_BYTES); + for (;;) { + const count = readSync(handle, buffer, 0, buffer.length, totalRead); + if (count === 0) break; + if (count < 0) { return indeterminate(surface, resolved.path, "rollout read ended before the observed size"); } - offset += read; + totalRead += count; + partial += buffer.toString("utf8", 0, count); + let newline = partial.indexOf("\n"); + while (newline !== -1) { + const line = partial.slice(0, newline); + partial = partial.slice(newline + 1); + if (line.trim()) { + const payload = rolloutSessionMetaPayload(line); + if (payload.kind === "malformed") { + return indeterminate(surface, resolved.path, payload.reason); + } + if (payload.payload !== null) { + first ??= payload.payload; + latest = payload.payload; + } + } + newline = partial.indexOf("\n"); + } + } + if (partial.trim()) { + const payload = rolloutSessionMetaPayload(partial); + if (payload.kind === "malformed") { + return indeterminate(surface, resolved.path, payload.reason); + } + if (payload.payload !== null) { + first ??= payload.payload; + latest = payload.payload; + } } const after = fstatSync(handle); if (!sameStat(resolved.stat, after)) { return indeterminate(surface, resolved.path, "rollout changed while it was being observed"); } - content = buffer.toString("utf8"); } catch (error) { if (errorCode(error) === "ENOENT") { return indeterminate(surface, resolved.path, "referenced rollout is absent"); @@ -419,29 +469,6 @@ function classifyReferencedRollout( } } - let first: Record | undefined; - let latest: Record | undefined; - - for (const line of content.split("\n")) { - if (!line.trim()) continue; - let parsed: unknown; - try { - parsed = JSON.parse(line); - } catch (error) { - return indeterminate(surface, resolved.path, `malformed rollout JSONL: ${errorReason(error)}`); - } - if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { - return indeterminate(surface, resolved.path, "rollout JSONL record is not an object"); - } - const record = parsed as Record; - if (record.type !== "session_meta") continue; - if (!record.payload || typeof record.payload !== "object" || Array.isArray(record.payload)) { - return indeterminate(surface, resolved.path, "session_meta payload has an unknown shape"); - } - const payload = record.payload as Record; - first ??= payload; - latest = payload; - } if (!first || !latest) { return indeterminate(surface, resolved.path, "referenced rollout has no session_meta metadata"); } diff --git a/tests/codex-native-residue.test.ts b/tests/codex-native-residue.test.ts index 437c3dadbc..59c957ec7a 100644 --- a/tests/codex-native-residue.test.ts +++ b/tests/codex-native-residue.test.ts @@ -659,6 +659,17 @@ test("a referenced rollout with native first and latest metadata is clean", () = expect(classifyNativeRoutedResidue()).toEqual({ kind: "clean" }); }); +test("a routed rollout without a trailing newline is residue", () => { + createHistoryDatabase("openai"); + writeFileSync(pathInCodexHome("rollout.jsonl"), sessionMeta("thread-1", "opencodex")); + + expect(classifyNativeRoutedResidue()).toMatchObject({ + kind: "residue", + surface: "history", + path: pathInCodexHome("rollout.jsonl"), + }); +}); + test("an oversized referenced rollout is indeterminate without being loaded", () => { createHistoryDatabase("openai"); truncateSync(pathInCodexHome("rollout.jsonl"), 64 * 1024 * 1024 + 1); From bbdeb199bfd38d26e688ea4d00dbc31c1afd274e Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 6 Aug 2026 14:27:00 +0200 Subject: [PATCH 4/5] fix(codex): bound rollout reads to the observed size and decode UTF-8 in stream --- src/codex/native-residue.ts | 13 +++++++++---- tests/codex-native-residue.test.ts | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/codex/native-residue.ts b/src/codex/native-residue.ts index 0d3e8c72c1..fb50ff68ec 100644 --- a/src/codex/native-residue.ts +++ b/src/codex/native-residue.ts @@ -416,15 +416,19 @@ function classifyReferencedRollout( `referenced rollout exceeds the ${MAX_ROLLOUT_INSPECTION_BYTES} byte inspection limit`, ); } + const decoder = new TextDecoder(); const buffer = Buffer.allocUnsafe(ROLLOUT_READ_CHUNK_BYTES); - for (;;) { - const count = readSync(handle, buffer, 0, buffer.length, totalRead); - if (count === 0) break; + while (totalRead < opened.size) { + const remaining = Math.min(buffer.length, opened.size - totalRead); + const count = readSync(handle, buffer, 0, remaining, totalRead); + if (count === 0) { + return indeterminate(surface, resolved.path, "rollout read ended before the observed size"); + } if (count < 0) { return indeterminate(surface, resolved.path, "rollout read ended before the observed size"); } totalRead += count; - partial += buffer.toString("utf8", 0, count); + partial += decoder.decode(buffer.subarray(0, count), { stream: true }); let newline = partial.indexOf("\n"); while (newline !== -1) { const line = partial.slice(0, newline); @@ -442,6 +446,7 @@ function classifyReferencedRollout( newline = partial.indexOf("\n"); } } + partial += decoder.decode(); if (partial.trim()) { const payload = rolloutSessionMetaPayload(partial); if (payload.kind === "malformed") { diff --git a/tests/codex-native-residue.test.ts b/tests/codex-native-residue.test.ts index 59c957ec7a..ed44fdef7f 100644 --- a/tests/codex-native-residue.test.ts +++ b/tests/codex-native-residue.test.ts @@ -670,6 +670,24 @@ test("a routed rollout without a trailing newline is residue", () => { }); }); +test("a routed rollout with a non-ASCII id split across the read chunk is residue", () => { + createHistoryDatabase("openai"); + const boundary = 64 * 1024; + const prefix = `{"timestamp":"2026-08-04T00:00:00.000Z","type":"session_meta","payload":{"description":"`; + const suffix = `","id":"thread-1","model_provider":"opencodex","source":"cli"}}\n`; + const paddingLength = boundary - Buffer.byteLength(prefix) - 1; // 🚀 starts at byte 65535, straddling 64 KiB + const content = `${prefix}${"x".repeat(paddingLength)}🚀${suffix}`; + const emojiByteOffset = Buffer.from(content, "utf8").indexOf(Buffer.from("🚀", "utf8")); + expect(emojiByteOffset).toBe(boundary - 1); + writeFileSync(pathInCodexHome("rollout.jsonl"), content); + + expect(classifyNativeRoutedResidue()).toMatchObject({ + kind: "residue", + surface: "history", + path: pathInCodexHome("rollout.jsonl"), + }); +}); + test("an oversized referenced rollout is indeterminate without being loaded", () => { createHistoryDatabase("openai"); truncateSync(pathInCodexHome("rollout.jsonl"), 64 * 1024 * 1024 + 1); From 3a3323b3dd0ff01f07c7a41c64efce67da024aaf Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 6 Aug 2026 14:39:40 +0200 Subject: [PATCH 5/5] fix(codex): re-stat rollout pathname, scan lines once, and preserve BOM --- src/codex/native-residue.ts | 59 +++++++++++++++++++++--------- tests/codex-native-residue.test.ts | 12 ++++++ 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/codex/native-residue.ts b/src/codex/native-residue.ts index fb50ff68ec..cc54252154 100644 --- a/src/codex/native-residue.ts +++ b/src/codex/native-residue.ts @@ -173,6 +173,33 @@ function rolloutSessionMetaPayload( return { kind: "payload", payload: record.payload as Record }; } +function consumeRolloutLines( + surface: "history" | "history-backup", + path: string, + partial: string, + first: Record | undefined, + latest: Record | undefined, +): NativeRoutedResidueResult | { kind: "continue"; partial: string; first: Record | undefined; latest: Record | undefined } { + let rest = partial; + let newline = rest.indexOf("\n"); + while (newline !== -1) { + const line = rest.slice(0, newline); + rest = rest.slice(newline + 1); + if (line.trim()) { + const payload = rolloutSessionMetaPayload(line); + if (payload.kind === "malformed") { + return indeterminate(surface, path, payload.reason); + } + if (payload.payload !== null) { + first ??= payload.payload; + latest = payload.payload; + } + } + newline = rest.indexOf("\n"); + } + return { kind: "continue", partial: rest, first, latest }; +} + function classifyToml( surface: "config" | "profile", path: string, @@ -416,7 +443,7 @@ function classifyReferencedRollout( `referenced rollout exceeds the ${MAX_ROLLOUT_INSPECTION_BYTES} byte inspection limit`, ); } - const decoder = new TextDecoder(); + const decoder = new TextDecoder("utf-8", { ignoreBOM: true }); const buffer = Buffer.allocUnsafe(ROLLOUT_READ_CHUNK_BYTES); while (totalRead < opened.size) { const remaining = Math.min(buffer.length, opened.size - totalRead); @@ -429,24 +456,18 @@ function classifyReferencedRollout( } totalRead += count; partial += decoder.decode(buffer.subarray(0, count), { stream: true }); - let newline = partial.indexOf("\n"); - while (newline !== -1) { - const line = partial.slice(0, newline); - partial = partial.slice(newline + 1); - if (line.trim()) { - const payload = rolloutSessionMetaPayload(line); - if (payload.kind === "malformed") { - return indeterminate(surface, resolved.path, payload.reason); - } - if (payload.payload !== null) { - first ??= payload.payload; - latest = payload.payload; - } - } - newline = partial.indexOf("\n"); - } + const consumed = consumeRolloutLines(surface, resolved.path, partial, first, latest); + if (consumed.kind !== "continue") return consumed; + partial = consumed.partial; + first = consumed.first; + latest = consumed.latest; } partial += decoder.decode(); + const consumed = consumeRolloutLines(surface, resolved.path, partial, first, latest); + if (consumed.kind !== "continue") return consumed; + partial = consumed.partial; + first = consumed.first; + latest = consumed.latest; if (partial.trim()) { const payload = rolloutSessionMetaPayload(partial); if (payload.kind === "malformed") { @@ -461,6 +482,10 @@ function classifyReferencedRollout( if (!sameStat(resolved.stat, after)) { return indeterminate(surface, resolved.path, "rollout changed while it was being observed"); } + const pathAfter = statSync(resolved.path); + if (!sameStat(resolved.stat, pathAfter)) { + return indeterminate(surface, resolved.path, "rollout pathname was replaced while it was being observed"); + } } catch (error) { if (errorCode(error) === "ENOENT") { return indeterminate(surface, resolved.path, "referenced rollout is absent"); diff --git a/tests/codex-native-residue.test.ts b/tests/codex-native-residue.test.ts index ed44fdef7f..b8d6e9ffa7 100644 --- a/tests/codex-native-residue.test.ts +++ b/tests/codex-native-residue.test.ts @@ -700,6 +700,18 @@ test("an oversized referenced rollout is indeterminate without being loaded", () }); }); +test("a BOM-prefixed rollout record is indeterminate", () => { + createHistoryDatabase("openai"); + writeFileSync(pathInCodexHome("rollout.jsonl"), `\uFEFF${sessionMeta("thread-1", "opencodex")}\n`); + + expect(classifyNativeRoutedResidue()).toMatchObject({ + kind: "indeterminate", + surface: "history", + path: pathInCodexHome("rollout.jsonl"), + reason: expect.stringContaining("malformed rollout JSONL"), + }); +}); + for (const fixture of [ { name: "missing",