```ts
export function planRetryWindow(policy: { base: number; factor: number; ceiling: number; tries: number; deadline: number }, outcomes: string[]): { times: number[]; verdict: string } {
    const { base, factor, ceiling, tries, deadline } = policy;

    if (typeof base !== 'number' || base < 1) throw new Error('base must be a whole number of one or more');
    if (typeof factor !== 'number' || factor < 1) throw new Error('factor must be a whole number of one or more');
    if (typeof ceiling !== 'number' || ceiling < base) throw new Error('ceiling must be a whole number of at least base');
    if (typeof tries !== 'number' || tries < 1) throw new Error('tries must be a whole number of one or more');
    if (typeof deadline !== 'number') throw new Error('deadline must be a number');

    if (!Array.isArray(outcomes)) throw new Error('outcomes must be a list');

    const validOutcomes = ['won', 'lost', 'held'];
    for (const outcome of outcomes) {
        if (!validOutcomes.includes(outcome)) throw new Error('each outcome must be one of won, lost, or held');
    }

    const times: number[] = [];
    let nextAttemptTime = 0;
    let consecutiveLost = 0;

    for (let attempt = 0; attempt < tries && outcomes.length > 0; attempt++) {
        const outcome = outcomes.shift()!;
        times.push(nextAttemptTime);

        if (outcome === 'won') {
            return { times, verdict: 'succeeded' };
        } else if (outcome === 'lost') {
            consecutiveLost++;
            nextAttemptTime += Math.min(base * Math.pow(factor, consecutiveLost - 1), ceiling);
        } else if (outcome === 'held') {
            consecutiveLost = 0;
            nextAttemptTime += ceiling;
        }

        if (nextAttemptTime >= deadline) {
            return { times, verdict: 'expired' };
        }
    }

    return { times, verdict: 'exhausted' };
}
```