```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 ||
    typeof factor !== 'number' || factor < 1 ||
    typeof ceiling !== 'number' || ceiling < base ||
    typeof tries !== 'number' || tries < 1 ||
    typeof deadline !== 'number' || deadline < 0
  ) {
    throw new Error('Invalid policy');
  }

  if (!Array.isArray(outcomes) || outcomes.some(outcome => !['won', 'lost', 'held'].includes(outcome))) {
    throw new Error('Invalid outcomes');
  }

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

  for (let i = 0; i < outcomes.length && times.length < tries; i++) {
    if (outcomes[i] === 'won') {
      return { times, verdict: 'succeeded' };
    }

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

    times.push(nextAttemptTime);

    if (outcomes[i] === 'lost') {
      consecutiveLost++;
      nextAttemptTime += Math.min(base * Math.pow(factor, consecutiveLost - 1), ceiling);
    } else if (outcomes[i] === 'held') {
      consecutiveLost = 0;
      nextAttemptTime += ceiling;
    }
  }

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