mirror of
https://github.com/stablyai/orca.git
synced 2026-10-26 16:02:42 +00:00
fix(codex): publish Windows system-default sessions (#12611)
* fix(codex): publish Windows system-default sessions * fix(codex): close launch-scheduling races in session migration scheduler * fix(codex): bound repeated session migration audits * fix(codex): preserve delayed session publication passes * fix(codex): bound failed session audit events * fix(codex): fence stale session migration markers * chore: preserve main formatting after merge * perf(codex): bound launch session migration scans * perf(codex): preserve coalesced migration scope * fix(codex): preserve scheduled migration recovery * fix(codex): preserve session migration recovery * fix(codex): close session migration launch races * fix(codex): harden session migration completion
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
appendFileSync,
|
||||
existsSync,
|
||||
lstatSync,
|
||||
mkdirSync,
|
||||
@@ -23,12 +24,14 @@ const { homedirMock } = vi.hoisted(() => ({
|
||||
const { fsMockState } = vi.hoisted(() => ({
|
||||
fsMockState: {
|
||||
failLink: false,
|
||||
failInstallLink: false,
|
||||
failInstallLinkTransiently: false,
|
||||
failLinkTransiently: false,
|
||||
failLinkPermission: false,
|
||||
raceTargetIntoExistence: false,
|
||||
failCopy: false,
|
||||
failMarkerRm: false,
|
||||
failMarkerReplacement: false,
|
||||
failAuditMkdirOnce: false,
|
||||
failAuditWrites: false,
|
||||
failMkdirPath: null as string | null,
|
||||
failDirectoryPath: null as string | null,
|
||||
failLstatPath: null as string | null
|
||||
}
|
||||
@@ -43,6 +46,30 @@ vi.mock('node:fs', async () => {
|
||||
return false
|
||||
}
|
||||
return actual.existsSync(...args)
|
||||
},
|
||||
rmSync: (...args: Parameters<typeof actual.rmSync>) => {
|
||||
if (
|
||||
fsMockState.failMarkerRm &&
|
||||
String(args[0]).includes('codex-session-backfill') &&
|
||||
String(args[0]).endsWith('backfill-complete.json')
|
||||
) {
|
||||
const error = new Error('EACCES: marker removal failed') as NodeJS.ErrnoException
|
||||
error.code = 'EACCES'
|
||||
throw error
|
||||
}
|
||||
return actual.rmSync(...args)
|
||||
},
|
||||
renameSync: (...args: Parameters<typeof actual.renameSync>) => {
|
||||
if (
|
||||
fsMockState.failMarkerReplacement &&
|
||||
String(args[1]).includes('codex-session-backfill') &&
|
||||
String(args[1]).endsWith('backfill-complete.json')
|
||||
) {
|
||||
const error = new Error('EACCES: marker replacement failed') as NodeJS.ErrnoException
|
||||
error.code = 'EACCES'
|
||||
throw error
|
||||
}
|
||||
return actual.renameSync(...args)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -52,6 +79,11 @@ vi.mock('node:fs/promises', async () => {
|
||||
return {
|
||||
...actual,
|
||||
mkdir: (...args: Parameters<typeof actual.mkdir>) => {
|
||||
if (args[0] === fsMockState.failMkdirPath) {
|
||||
const error = new Error('EACCES: target directory inaccessible') as NodeJS.ErrnoException
|
||||
error.code = 'EACCES'
|
||||
throw error
|
||||
}
|
||||
if (fsMockState.failAuditMkdirOnce && String(args[0]).includes('codex-session-backfill')) {
|
||||
fsMockState.failAuditMkdirOnce = false
|
||||
const error = new Error(
|
||||
@@ -91,30 +123,17 @@ vi.mock('node:fs/promises', async () => {
|
||||
error.code = 'EXDEV'
|
||||
throw error
|
||||
}
|
||||
// Simulate a target filesystem with no hardlink support: even the
|
||||
// same-volume staged-copy install link (.orca-backfill-*.tmp) fails.
|
||||
if (fsMockState.failInstallLink && String(args[0]).includes('.orca-backfill-')) {
|
||||
const error = new Error('EPERM: hardlinks unsupported') as NodeJS.ErrnoException
|
||||
error.code = 'EPERM'
|
||||
throw error
|
||||
}
|
||||
if (fsMockState.failInstallLinkTransiently && String(args[0]).includes('.orca-backfill-')) {
|
||||
const error = new Error('EIO: transient install failure') as NodeJS.ErrnoException
|
||||
if (fsMockState.failLinkTransiently && String(args[0]).includes('codex-runtime-home')) {
|
||||
const error = new Error('EIO: transient hardlink failure') as NodeJS.ErrnoException
|
||||
error.code = 'EIO'
|
||||
throw error
|
||||
}
|
||||
return actual.link(...args)
|
||||
},
|
||||
copyFile: async (...args: Parameters<typeof actual.copyFile>) => {
|
||||
if (fsMockState.failCopy) {
|
||||
// Simulate a copy that fails after opening its destination, which is
|
||||
// the dangerous case for resumability rather than a preflight error.
|
||||
await actual.writeFile(args[1], 'partial copy\n', 'utf-8')
|
||||
const error = new Error('EACCES: copy disabled for test') as NodeJS.ErrnoException
|
||||
if (fsMockState.failLinkPermission && String(args[0]).includes('codex-runtime-home')) {
|
||||
const error = new Error('EACCES: hardlink permission denied') as NodeJS.ErrnoException
|
||||
error.code = 'EACCES'
|
||||
throw error
|
||||
}
|
||||
return actual.copyFile(...args)
|
||||
return actual.link(...args)
|
||||
},
|
||||
opendir: (...args: Parameters<typeof actual.opendir>) => {
|
||||
if (args[0] === fsMockState.failDirectoryPath) {
|
||||
@@ -140,6 +159,7 @@ import {
|
||||
resolveCodexSessionBackfillPaths,
|
||||
startCodexSessionBackfillInBackground
|
||||
} from './codex-session-backfill'
|
||||
import { invalidateCodexSessionBackfillMarker } from './codex-session-backfill-marker'
|
||||
|
||||
let fakeHomeDir: string
|
||||
let userDataDir: string
|
||||
@@ -168,27 +188,40 @@ function writeManagedSession(relativePath: string, contents: string): string {
|
||||
return filePath
|
||||
}
|
||||
|
||||
function readAuditActions(): string[] {
|
||||
type BackfillAuditRecord = {
|
||||
action: string
|
||||
target?: string
|
||||
fileEventId?: string
|
||||
diagnosticEventId?: string
|
||||
}
|
||||
|
||||
function readBackfillAuditRecords(): BackfillAuditRecord[] {
|
||||
return readFileSync(getAuditLogPath(), 'utf-8')
|
||||
.split('\n')
|
||||
.filter(Boolean)
|
||||
.flatMap((line) => {
|
||||
try {
|
||||
return [(JSON.parse(line) as { action: string }).action]
|
||||
return [JSON.parse(line) as BackfillAuditRecord]
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function readAuditActions(): string[] {
|
||||
return readBackfillAuditRecords().map((record) => record.action)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
fsMockState.failLink = false
|
||||
fsMockState.failInstallLink = false
|
||||
fsMockState.failInstallLinkTransiently = false
|
||||
fsMockState.failLinkTransiently = false
|
||||
fsMockState.failLinkPermission = false
|
||||
fsMockState.raceTargetIntoExistence = false
|
||||
fsMockState.failCopy = false
|
||||
fsMockState.failMarkerRm = false
|
||||
fsMockState.failMarkerReplacement = false
|
||||
fsMockState.failAuditMkdirOnce = false
|
||||
fsMockState.failAuditWrites = false
|
||||
fsMockState.failMkdirPath = null
|
||||
fsMockState.failDirectoryPath = null
|
||||
fsMockState.failLstatPath = null
|
||||
fakeHomeDir = mkdtempSync(join(tmpdir(), 'orca-codex-backfill-home-'))
|
||||
@@ -367,27 +400,22 @@ describe('backfillManagedCodexSessionsIntoSystemHome', () => {
|
||||
expect(readAuditActions()).toEqual(['hardlink', 'run-summary'])
|
||||
})
|
||||
|
||||
it('falls back to copy when hardlinking fails across volumes', async () => {
|
||||
it('skips cross-volume rollouts instead of freezing a mutable snapshot', async () => {
|
||||
fsMockState.failLink = true
|
||||
const managedPath = writeManagedSession(
|
||||
join('2026', '05', '26', 'rollout-a ü.jsonl'),
|
||||
'{"id":"a"}\n'
|
||||
)
|
||||
const relativePath = join('2026', '05', '26', 'rollout-a ü.jsonl')
|
||||
writeManagedSession(relativePath, '{"id":"a"}\n')
|
||||
|
||||
const summary = await backfillManagedCodexSessionsIntoSystemHome(
|
||||
resolveCodexSessionBackfillPaths()
|
||||
)
|
||||
|
||||
expect(summary).toMatchObject({ linkedFiles: 0, copiedFiles: 1, failedFiles: 0 })
|
||||
const targetPath = join(getSystemSessionsRoot(), '2026', '05', '26', 'rollout-a ü.jsonl')
|
||||
expect(readFileSync(targetPath, 'utf-8')).toBe(readFileSync(managedPath, 'utf-8'))
|
||||
expect(lstatSync(targetPath).ino).not.toBe(lstatSync(managedPath).ino)
|
||||
expect(readAuditActions()).toEqual(['copy', 'run-summary'])
|
||||
expect(summary).toMatchObject({ copiedFiles: 0, skippedUnsupportedFilesystemFiles: 1 })
|
||||
expect(existsSync(join(getSystemSessionsRoot(), relativePath))).toBe(false)
|
||||
expect(readAuditActions()).toEqual(['copy-unsupported', 'run-summary'])
|
||||
})
|
||||
|
||||
it('fails closed when the target filesystem cannot install without overwrite', async () => {
|
||||
fsMockState.failLink = true
|
||||
fsMockState.failInstallLink = true
|
||||
writeManagedSession(join('2026', '05', '26', 'rollout-a.jsonl'), '{"id":"a"}\n')
|
||||
|
||||
const summary = await backfillManagedCodexSessionsIntoSystemHome(
|
||||
@@ -406,9 +434,8 @@ describe('backfillManagedCodexSessionsIntoSystemHome', () => {
|
||||
expect(readAuditActions()).toEqual(['copy-unsupported', 'run-summary'])
|
||||
})
|
||||
|
||||
it('keeps transient install failures retryable', async () => {
|
||||
fsMockState.failLink = true
|
||||
fsMockState.failInstallLinkTransiently = true
|
||||
it('keeps transient hardlink failures retryable', async () => {
|
||||
fsMockState.failLinkTransiently = true
|
||||
writeManagedSession(join('2026', '05', '26', 'rollout-a.jsonl'), '{"id":"a"}\n')
|
||||
|
||||
const summary = await backfillManagedCodexSessionsIntoSystemHome(
|
||||
@@ -422,9 +449,33 @@ describe('backfillManagedCodexSessionsIntoSystemHome', () => {
|
||||
expect(readAuditActions()).toEqual(['failed', 'run-summary'])
|
||||
})
|
||||
|
||||
it('keeps target directory permission failures retryable', async () => {
|
||||
const relativePath = join('2026', '05', '26', 'rollout-a.jsonl')
|
||||
writeManagedSession(relativePath, '{"id":"a"}\n')
|
||||
fsMockState.failMkdirPath = dirname(join(getSystemSessionsRoot(), relativePath))
|
||||
|
||||
const summary = await startCodexSessionBackfillInBackground()
|
||||
|
||||
expect(summary).toMatchObject({ failedFiles: 1, skippedUnsupportedFilesystemFiles: 0 })
|
||||
expect(existsSync(join(getSystemSessionsRoot(), relativePath))).toBe(false)
|
||||
expect(existsSync(getMarkerPath())).toBe(false)
|
||||
expect(readAuditActions()).toEqual(['failed', 'run-summary'])
|
||||
})
|
||||
|
||||
it('keeps hardlink permission failures retryable', async () => {
|
||||
fsMockState.failLinkPermission = true
|
||||
const relativePath = join('2026', '05', '26', 'rollout-a.jsonl')
|
||||
writeManagedSession(relativePath, '{"id":"a"}\n')
|
||||
|
||||
const summary = await startCodexSessionBackfillInBackground()
|
||||
|
||||
expect(summary).toMatchObject({ failedFiles: 1, skippedUnsupportedFilesystemFiles: 0 })
|
||||
expect(existsSync(join(getSystemSessionsRoot(), relativePath))).toBe(false)
|
||||
expect(existsSync(getMarkerPath())).toBe(false)
|
||||
})
|
||||
|
||||
it('records per-file failures without aborting the run', async () => {
|
||||
fsMockState.failLink = true
|
||||
fsMockState.failCopy = true
|
||||
fsMockState.failLinkTransiently = true
|
||||
writeManagedSession(join('2026', '05', '26', 'rollout-a.jsonl'), '{"id":"a"}\n')
|
||||
|
||||
const summary = await backfillManagedCodexSessionsIntoSystemHome(
|
||||
@@ -446,6 +497,33 @@ describe('backfillManagedCodexSessionsIntoSystemHome', () => {
|
||||
expect(summary).toMatchObject({ scannedFiles: 0 })
|
||||
expect(existsSync(getSystemSessionsRoot())).toBe(false)
|
||||
})
|
||||
|
||||
it('bounds a launch pass to its rollout date directories', async () => {
|
||||
const oldRelativePath = join('2025', '12', '31', 'rollout-old.jsonl')
|
||||
const launchRelativePath = join('2026', '08', '05', 'rollout-launch.jsonl')
|
||||
writeManagedSession(oldRelativePath, 'old\n')
|
||||
writeManagedSession(launchRelativePath, 'launch\n')
|
||||
|
||||
const summary = await backfillManagedCodexSessionsIntoSystemHome(
|
||||
resolveCodexSessionBackfillPaths(),
|
||||
{ scanDates: [['2026', '08', '05']] }
|
||||
)
|
||||
|
||||
expect(summary).toMatchObject({ scannedFiles: 1, linkedFiles: 1, failedDirectories: 0 })
|
||||
expect(existsSync(join(getSystemSessionsRoot(), launchRelativePath))).toBe(true)
|
||||
expect(existsSync(join(getSystemSessionsRoot(), oldRelativePath))).toBe(false)
|
||||
})
|
||||
|
||||
it('treats a not-yet-created launch date as an empty bounded pass', async () => {
|
||||
writeManagedSession(join('2025', '12', '31', 'rollout-old.jsonl'), 'old\n')
|
||||
|
||||
const summary = await backfillManagedCodexSessionsIntoSystemHome(
|
||||
resolveCodexSessionBackfillPaths(),
|
||||
{ scanDates: [['2026', '08', '05']] }
|
||||
)
|
||||
|
||||
expect(summary).toMatchObject({ scannedFiles: 0, linkedFiles: 0, failedDirectories: 0 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('startCodexSessionBackfillInBackground', () => {
|
||||
@@ -479,6 +557,81 @@ describe('startCodexSessionBackfillInBackground', () => {
|
||||
expect(existsSync(getMarkerPath())).toBe(false)
|
||||
})
|
||||
|
||||
it('defers completion while a launch lease is active', async () => {
|
||||
writeManagedSession(join('2026', '05', '26', 'rollout-a.jsonl'), '{"id":"a"}\n')
|
||||
|
||||
const active = await startCodexSessionBackfillInBackground({
|
||||
writeCompletionMarker: false
|
||||
})
|
||||
expect(active).toMatchObject({ linkedFiles: 1 })
|
||||
expect(existsSync(getMarkerPath())).toBe(false)
|
||||
|
||||
const completed = await startCodexSessionBackfillInBackground()
|
||||
expect(completed).toMatchObject({ skippedExistingFiles: 1 })
|
||||
expect(existsSync(getMarkerPath())).toBe(true)
|
||||
})
|
||||
|
||||
it('rechecks launch state before publishing completion', async () => {
|
||||
writeManagedSession(join('2026', '05', '26', 'rollout-a.jsonl'), '{"id":"a"}\n')
|
||||
|
||||
const summary = await startCodexSessionBackfillInBackground({
|
||||
canWriteCompletionMarker: () => false
|
||||
})
|
||||
|
||||
expect(summary).toMatchObject({ linkedFiles: 1 })
|
||||
expect(existsSync(getMarkerPath())).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps an invalidated active pass from recreating the completion marker', async () => {
|
||||
writeManagedSession(join('2026', '05', '26', 'rollout-a.jsonl'), '{"id":"a"}\n')
|
||||
let invalidated = false
|
||||
|
||||
const raced = await startCodexSessionBackfillInBackground({
|
||||
shouldStop: () => {
|
||||
if (!invalidated) {
|
||||
invalidated = true
|
||||
invalidateCodexSessionBackfillMarker(getMarkerPath())
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
expect(raced).toMatchObject({ linkedFiles: 1, stopped: false })
|
||||
expect(existsSync(getMarkerPath())).toBe(false)
|
||||
const racedAudit = readFileSync(getAuditLogPath(), 'utf-8')
|
||||
|
||||
const recovered = await startCodexSessionBackfillInBackground()
|
||||
|
||||
expect(recovered).toMatchObject({ skippedExistingFiles: 1, failedHealAuditRecords: 0 })
|
||||
expect(existsSync(getMarkerPath())).toBe(true)
|
||||
expect(readFileSync(getAuditLogPath(), 'utf-8')).toBe(racedAudit)
|
||||
})
|
||||
|
||||
it('replaces a stale marker when direct removal fails', async () => {
|
||||
writeManagedSession(join('2026', '05', '26', 'rollout-a.jsonl'), '{"id":"a"}\n')
|
||||
await startCodexSessionBackfillInBackground()
|
||||
fsMockState.failMarkerRm = true
|
||||
|
||||
invalidateCodexSessionBackfillMarker(getMarkerPath())
|
||||
|
||||
expect(JSON.parse(readFileSync(getMarkerPath(), 'utf-8'))).toMatchObject({ version: 0 })
|
||||
const recovered = await startCodexSessionBackfillInBackground()
|
||||
expect(recovered).toMatchObject({ skippedExistingFiles: 1 })
|
||||
expect(JSON.parse(readFileSync(getMarkerPath(), 'utf-8'))).toMatchObject({ version: 3 })
|
||||
})
|
||||
|
||||
it('fails launch preparation when a stale marker cannot be invalidated', async () => {
|
||||
writeManagedSession(join('2026', '05', '26', 'rollout-a.jsonl'), '{"id":"a"}\n')
|
||||
await startCodexSessionBackfillInBackground()
|
||||
fsMockState.failMarkerRm = true
|
||||
fsMockState.failMarkerReplacement = true
|
||||
|
||||
expect(() => invalidateCodexSessionBackfillMarker(getMarkerPath())).toThrow(
|
||||
'Failed to invalidate Codex session backfill marker'
|
||||
)
|
||||
expect(JSON.parse(readFileSync(getMarkerPath(), 'utf-8'))).toMatchObject({ version: 3 })
|
||||
})
|
||||
|
||||
it('writes a completion marker and skips the walk on later runs', async () => {
|
||||
writeManagedSession(join('2026', '05', '26', 'rollout-a.jsonl'), '{"id":"a"}\n')
|
||||
|
||||
@@ -487,13 +640,160 @@ describe('startCodexSessionBackfillInBackground', () => {
|
||||
expect(existsSync(getMarkerPath())).toBe(true)
|
||||
expect(JSON.parse(readFileSync(getMarkerPath(), 'utf-8'))).toMatchObject({ version: 3 })
|
||||
|
||||
// A file appearing after the marker must not be backfilled again.
|
||||
// An ordinary call remains a no-op; only a launch-scheduled pass bypasses the marker.
|
||||
writeManagedSession(join('2026', '07', '01', 'rollout-later.jsonl'), '{"id":"later"}\n')
|
||||
const second = await startCodexSessionBackfillInBackground()
|
||||
expect(second).toBeNull()
|
||||
expect(
|
||||
existsSync(join(getSystemSessionsRoot(), '2026', '07', '01', 'rollout-later.jsonl'))
|
||||
).toBe(false)
|
||||
|
||||
const scheduled = await startCodexSessionBackfillInBackground({
|
||||
ignoreCompletionMarker: true,
|
||||
scanDates: [['2026', '07', '01']]
|
||||
})
|
||||
expect(scheduled).toMatchObject({ scannedFiles: 1, linkedFiles: 1 })
|
||||
})
|
||||
|
||||
it('does not let a bounded pass certify older unscanned history', async () => {
|
||||
writeManagedSession(join('2026', '05', '26', 'rollout-baseline.jsonl'), 'baseline\n')
|
||||
await startCodexSessionBackfillInBackground()
|
||||
|
||||
invalidateCodexSessionBackfillMarker(getMarkerPath())
|
||||
const missedRelativePath = join('2026', '06', '01', 'rollout-missed.jsonl')
|
||||
writeManagedSession(missedRelativePath, 'missed\n')
|
||||
writeManagedSession(join('2026', '08', '05', 'rollout-launch.jsonl'), 'launch\n')
|
||||
|
||||
const bounded = await startCodexSessionBackfillInBackground({
|
||||
ignoreCompletionMarker: true,
|
||||
scanDates: [['2026', '08', '05']]
|
||||
})
|
||||
expect(bounded).toMatchObject({ scannedFiles: 1, linkedFiles: 1 })
|
||||
expect(existsSync(getMarkerPath())).toBe(false)
|
||||
|
||||
const recovered = await startCodexSessionBackfillInBackground()
|
||||
expect(recovered).toMatchObject({ scannedFiles: 3, linkedFiles: 1 })
|
||||
expect(existsSync(join(getSystemSessionsRoot(), missedRelativePath))).toBe(true)
|
||||
expect(existsSync(getMarkerPath())).toBe(true)
|
||||
})
|
||||
|
||||
it('lets an explicit bounded final pass restore a certified baseline', async () => {
|
||||
writeManagedSession(join('2026', '05', '26', 'rollout-baseline.jsonl'), 'baseline\n')
|
||||
await startCodexSessionBackfillInBackground()
|
||||
|
||||
invalidateCodexSessionBackfillMarker(getMarkerPath())
|
||||
writeManagedSession(join('2026', '08', '05', 'rollout-launch.jsonl'), 'launch\n')
|
||||
|
||||
const bounded = await startCodexSessionBackfillInBackground({
|
||||
ignoreCompletionMarker: true,
|
||||
scanDates: [['2026', '08', '05']],
|
||||
writeBoundedCompletionMarker: true
|
||||
})
|
||||
|
||||
expect(bounded).toMatchObject({ scannedFiles: 1, linkedFiles: 1 })
|
||||
expect(existsSync(getMarkerPath())).toBe(true)
|
||||
})
|
||||
|
||||
it('records a new heal event when a linked rollout grows in place', async () => {
|
||||
const relativePath = join('2026', '05', '26', 'rollout-growing.jsonl')
|
||||
const managedPath = writeManagedSession(relativePath, '{"id":"a"}\n')
|
||||
await startCodexSessionBackfillInBackground()
|
||||
|
||||
const firstRecord = readBackfillAuditRecords().find((record) => record.action === 'hardlink')
|
||||
invalidateCodexSessionBackfillMarker(getMarkerPath())
|
||||
appendFileSync(managedPath, '{"event":"later"}\n', 'utf-8')
|
||||
await startCodexSessionBackfillInBackground()
|
||||
|
||||
const fileRecords = readBackfillAuditRecords().filter((record) =>
|
||||
['hardlink', 'existing'].includes(record.action)
|
||||
)
|
||||
expect(fileRecords).toHaveLength(2)
|
||||
expect(fileRecords[1]).toMatchObject({ action: 'existing' })
|
||||
expect(fileRecords[1]?.fileEventId).not.toBe(firstRecord?.fileEventId)
|
||||
})
|
||||
|
||||
it('keeps repeated launch invalidations audit-stable', async () => {
|
||||
writeManagedSession(join('2026', '05', '26', 'rollout-a.jsonl'), '{"id":"a"}\n')
|
||||
|
||||
const first = await startCodexSessionBackfillInBackground()
|
||||
expect(first).toMatchObject({ linkedFiles: 1, failedHealAuditRecords: 0 })
|
||||
const firstAudit = readFileSync(getAuditLogPath(), 'utf-8')
|
||||
|
||||
for (let pass = 0; pass < 2; pass += 1) {
|
||||
invalidateCodexSessionBackfillMarker(getMarkerPath())
|
||||
const repeated = await startCodexSessionBackfillInBackground()
|
||||
expect(repeated).toMatchObject({
|
||||
linkedFiles: 0,
|
||||
copiedFiles: 0,
|
||||
skippedExistingFiles: 1,
|
||||
failedHealAuditRecords: 0
|
||||
})
|
||||
expect(readFileSync(getAuditLogPath(), 'utf-8')).toBe(firstAudit)
|
||||
}
|
||||
|
||||
const fileRecords = readBackfillAuditRecords().filter((record) =>
|
||||
['hardlink', 'copy', 'existing'].includes(record.action)
|
||||
)
|
||||
expect(fileRecords).toEqual([
|
||||
expect.objectContaining({ action: 'hardlink', fileEventId: expect.any(String) })
|
||||
])
|
||||
})
|
||||
|
||||
it('recovers a post-install audit interruption without duplicating prior events', async () => {
|
||||
const firstRelativePath = join('2026', '05', '26', 'rollout-a.jsonl')
|
||||
const secondRelativePath = join('2026', '05', '26', 'rollout-b.jsonl')
|
||||
writeManagedSession(firstRelativePath, '{"id":"a"}\n')
|
||||
await startCodexSessionBackfillInBackground()
|
||||
|
||||
invalidateCodexSessionBackfillMarker(getMarkerPath())
|
||||
writeManagedSession(secondRelativePath, '{"id":"b"}\n')
|
||||
fsMockState.failAuditWrites = true
|
||||
|
||||
const interrupted = await startCodexSessionBackfillInBackground()
|
||||
|
||||
expect(interrupted).toMatchObject({ linkedFiles: 1, failedHealAuditRecords: 1 })
|
||||
expect(existsSync(getMarkerPath())).toBe(false)
|
||||
expect(
|
||||
readBackfillAuditRecords().filter((record) =>
|
||||
['hardlink', 'copy', 'existing'].includes(record.action)
|
||||
)
|
||||
).toHaveLength(1)
|
||||
|
||||
fsMockState.failAuditWrites = false
|
||||
const recovered = await startCodexSessionBackfillInBackground()
|
||||
|
||||
expect(recovered).toMatchObject({
|
||||
linkedFiles: 0,
|
||||
skippedExistingFiles: 2,
|
||||
failedHealAuditRecords: 0
|
||||
})
|
||||
expect(existsSync(getMarkerPath())).toBe(true)
|
||||
const recoveredFileRecords = readBackfillAuditRecords().filter((record) =>
|
||||
['hardlink', 'copy', 'existing'].includes(record.action)
|
||||
)
|
||||
expect(recoveredFileRecords).toHaveLength(2)
|
||||
expect(new Set(recoveredFileRecords.map((record) => record.target))).toEqual(
|
||||
new Set([
|
||||
join(getSystemSessionsRoot(), firstRelativePath),
|
||||
join(getSystemSessionsRoot(), secondRelativePath)
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it('self-heals a zero-file marker when managed rollouts appear later', async () => {
|
||||
const empty = await startCodexSessionBackfillInBackground()
|
||||
expect(empty).toMatchObject({ scannedFiles: 0, linkedFiles: 0 })
|
||||
expect(JSON.parse(readFileSync(getMarkerPath(), 'utf-8'))).toMatchObject({
|
||||
summary: { scannedFiles: 0 }
|
||||
})
|
||||
|
||||
writeManagedSession(join('2026', '07', '28', 'rollout-later.jsonl'), '{"id":"later"}\n')
|
||||
const healed = await startCodexSessionBackfillInBackground()
|
||||
|
||||
expect(healed).toMatchObject({ scannedFiles: 1, linkedFiles: 1 })
|
||||
expect(
|
||||
existsSync(join(getSystemSessionsRoot(), '2026', '07', '28', 'rollout-later.jsonl'))
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('recovers an installed rollout after the completion marker write fails', async () => {
|
||||
@@ -534,19 +834,52 @@ describe('startCodexSessionBackfillInBackground', () => {
|
||||
|
||||
it('does not retry a stable hardlink-less filesystem limitation', async () => {
|
||||
fsMockState.failLink = true
|
||||
fsMockState.failInstallLink = true
|
||||
writeManagedSession(join('2026', '05', '26', 'rollout-a.jsonl'), '{"id":"a"}\n')
|
||||
|
||||
const first = await startCodexSessionBackfillInBackground()
|
||||
expect(first).toMatchObject({ skippedUnsupportedFilesystemFiles: 1, failedFiles: 0 })
|
||||
expect(existsSync(getMarkerPath())).toBe(true)
|
||||
|
||||
const firstAudit = readFileSync(getAuditLogPath(), 'utf-8')
|
||||
invalidateCodexSessionBackfillMarker(getMarkerPath())
|
||||
const repeated = await startCodexSessionBackfillInBackground()
|
||||
|
||||
expect(repeated).toMatchObject({ skippedUnsupportedFilesystemFiles: 1, failedFiles: 0 })
|
||||
expect(readFileSync(getAuditLogPath(), 'utf-8')).toBe(firstAudit)
|
||||
|
||||
expect(await startCodexSessionBackfillInBackground()).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps repeated unchanged per-file failures audit-stable', async () => {
|
||||
fsMockState.failLinkTransiently = true
|
||||
writeManagedSession(join('2026', '05', '26', 'rollout-a.jsonl'), '{"id":"a"}\n')
|
||||
|
||||
const first = await startCodexSessionBackfillInBackground()
|
||||
expect(first).toMatchObject({ failedFiles: 1 })
|
||||
const firstAudit = readFileSync(getAuditLogPath(), 'utf-8')
|
||||
|
||||
const repeated = await startCodexSessionBackfillInBackground()
|
||||
|
||||
expect(repeated).toMatchObject({ failedFiles: 1 })
|
||||
expect(readFileSync(getAuditLogPath(), 'utf-8')).toBe(firstAudit)
|
||||
})
|
||||
|
||||
it('records a new failure event when the source file changes', async () => {
|
||||
fsMockState.failLinkTransiently = true
|
||||
const relativePath = join('2026', '05', '26', 'rollout-a.jsonl')
|
||||
writeManagedSession(relativePath, '{"id":"a"}\n')
|
||||
await startCodexSessionBackfillInBackground()
|
||||
|
||||
writeManagedSession(relativePath, '{"id":"a","changed":true}\n')
|
||||
await startCodexSessionBackfillInBackground()
|
||||
|
||||
const failedRecords = readBackfillAuditRecords().filter((record) => record.action === 'failed')
|
||||
expect(failedRecords).toHaveLength(2)
|
||||
expect(new Set(failedRecords.map((record) => record.diagnosticEventId)).size).toBe(2)
|
||||
})
|
||||
|
||||
it('leaves the marker unset when any file fails so the next startup retries', async () => {
|
||||
fsMockState.failLink = true
|
||||
fsMockState.failCopy = true
|
||||
fsMockState.failLinkTransiently = true
|
||||
writeManagedSession(join('2026', '05', '26', 'rollout-a.jsonl'), '{"id":"a"}\n')
|
||||
|
||||
const first = await startCodexSessionBackfillInBackground()
|
||||
@@ -555,8 +888,7 @@ describe('startCodexSessionBackfillInBackground', () => {
|
||||
const targetPath = join(getSystemSessionsRoot(), '2026', '05', '26', 'rollout-a.jsonl')
|
||||
expect(existsSync(targetPath)).toBe(false)
|
||||
|
||||
fsMockState.failLink = false
|
||||
fsMockState.failCopy = false
|
||||
fsMockState.failLinkTransiently = false
|
||||
const second = await startCodexSessionBackfillInBackground()
|
||||
expect(second).toMatchObject({ linkedFiles: 1, failedFiles: 0 })
|
||||
expect(readFileSync(targetPath, 'utf-8')).toBe('{"id":"a"}\n')
|
||||
|
||||
Reference in New Issue
Block a user