```ts
export function preemptFirstOverrun(jobs: Array<Record<string, unknown>>): string {
  const seen = new Set<string>();
  for (const job of jobs) {
    if (typeof job.name !== "string" || job.name === "") {
      throw new Error("name must be a non-empty string");
    }
    if (seen.has(job.name)) {
      throw new Error(`name repeated: ${job.name}`);
    }
    seen.add(job.name);
    if (!Number.isInteger(job.at) || (job.at as number) < 0) {
      throw new Error("at must be a non-negative integer");
    }
    if (!Number.isInteger(job.work) || (job.work as number) <= 0) {
      throw new Error("work must be a positive integer");
    }
    if (!Number.isInteger(job.due) || (job.due as number) <= 0) {
      throw new Error("due must be a positive integer");
    }
  }

  const remaining = new Map<string, number>();
  const at = new Map<string, number>();
  const due = new Map<string, number>();

  for (const job of jobs) {
    remaining.set(job.name as string, job.work as number);
    at.set(job.name as string, job.at as number);
    due.set(job.name as string, job.due as number);
  }

  const finish = new Map<string, number>();
  let clock = 0;
  let currentJob: string | null = null;

  while (finish.size < jobs.length) {
    const ready = [...remaining.keys()].filter(
      (name) => !finish.has(name) && at.get(name)! <= clock,
    );

    if (ready.length === 0) {
      clock = Math.min(
        ...[...remaining.keys()]
          .filter((name) => !finish.has(name))
          .map((name) => at.get(name)!),
      );
      currentJob = null;
      continue;
    }

    ready.sort((a, b) => due.get(a)! - due.get(b)! || (a < b ? -1 : 1));

    if (ready[0] !== currentJob) {
      // Preempt the current job and start the new one
      clock = at.get(ready[0])!;
      currentJob = ready[0];
    }

    const workLeft = remaining.get(currentJob)!;
    const timeToDue = due.get(currentJob)! - clock;

    if (workLeft <= timeToDue) {
      // Complete the job
      clock += workLeft;
      remaining.set(currentJob, 0);
      finish.set(currentJob, clock);
      currentJob = null;
    } else {
      // Overrun, run until due and preempt
      clock = due.get(currentJob)!;
      remaining.set(currentJob, workLeft - timeToDue);
    }
  }

  const missed = jobs.filter(
    (job) => finish.get(job.name as string)! > (job.due as number),
  );

  if (missed.length === 0) {
    return "";
  }

  missed.sort(
    (a, b) =>
      (a.due as number) - (b.due as number) ||
      ((a.name as string) < (b.name as string) ? -1 : 1),
  );

  return missed[0].name as string;
}
```