Design notes on the choices I made:

- **Dedup:** for each `key` value, the last job wins and keeps its own original index. Survivors are processed in original index order.
- **Cancelled:** `cancel_wins` is false, so a cancelled job goes to `cancelled` only if it is not retryable. A cancelled retryable job continues through the normal flow.
- **Dead:** a job is dead if `now - created > stale_after` (not inclusive), if `attempt >= max_attempts`, or if `last_code` is nonzero and not retryable.
- **Backoff:** `exponential` is false, so the factor is 1 and the delay is `min(base_delay, delay_cap)`. A retryable job has a nonzero `last_code` in `retryable_codes`, so it has a failure, and its due is `max(due, failed_at + delay)`. A job with `last_code` 0 keeps its due.
- **Ready ordering:** `priority_high_first` is false, so priority ascends. `fifo_ties` is false, so created descends. The final tiebreak is the original index.
- **Dead ordering:** `dead_order` is "input", so dead jobs are sorted by original index.
- **Waiting and cancelled:** both are sorted by original index. Waiting entries are `{id, at}`.

```json
{"files": {"main.ts": "import {run} from './core.ts';\nimport {POLICY} from './policy.ts';\nexport function evaluate(payload: any): any { return run(payload, POLICY); }\n", "policy.ts": "export const POLICY: any = {\n  base_delay: 200,\n  cancel_wins: false,\n  dead_order: 'input',\n  dedup_key: 'key',\n  delay_cap: 400,\n  exponential: false,\n  fifo_ties: false,\n  max_attempts: 4,\n  priority_high_first: false,\n  retryable_codes: [408, 503],\n  stale_after: 2000,\n  stale_inclusive: false,\n};\n", "helpers.ts": "export function dedupe(jobs: any[], field: string): {job: any, idx: number}[] {\n  const m = new Map<any, {job: any, idx: number}>();\n  jobs.forEach((job, idx) => { m.set(job[field], {job, idx}); });\n  return Array.from(m.values()).sort((a, b) => a.idx - b.idx);\n}\n", "core.ts": "import {dedupe} from './helpers.ts';\n\nexport function run(payload: any, policy: any): any {\n  const now = payload.now;\n  const items = dedupe(payload.jobs, policy.dedup_key);\n  const ready: any[] = [];\n  const waiting: any[] = [];\n  const dead: any[] = [];\n  const cancelled: any[] = [];\n  for (const it of items) {\n    const j = it.job;\n    const retryable = policy.retryable_codes.includes(j.last_code);\n    if (j.cancelled && (policy.cancel_wins || !retryable)) { cancelled.push(it); continue; }\n    const age = now - j.created;\n    const stale = policy.stale_inclusive ? age >= policy.stale_after : age > policy.stale_after;\n    if (stale || j.attempt >= policy.max_attempts || (j.last_code !== 0 && !retryable)) { dead.push(it); continue; }\n    let due = j.due;\n    if (retryable) {\n      const mult = policy.exponential ? Math.pow(2, Math.max(0, j.attempt - 1)) : 1;\n      const delay = Math.min(policy.base_delay * mult, policy.delay_cap);\n      due = Math.max(due, j.failed_at + delay);\n    }\n    if (due > now) waiting.push({it, at: due}); else ready.push(it);\n  }\n  const pd = policy.priority_high_first ? -1 : 1;\n  const cd = policy.fifo_ties ? 1 : -1;\n  ready.sort((a, b) => {\n    if (a.job.priority !== b.job.priority) return pd * (a.job.priority - b.job.priority);\n    if (a.job.created !== b.job.created) return cd * (a.job.created - b.job.created);\n    return a.idx - b.idx;\n  });\n  if (policy.dead_order === 'id') {\n    dead.sort((a, b) => a.job.id < b.job.id ? -1 : a.job.id > b.job.id ? 1 : a.idx - b.idx);\n  } else {\n    dead.sort((a, b) => a.idx - b.idx);\n  }\n  waiting.sort((a, b) => a.it.idx - b.it.idx);\n  cancelled.sort((a, b) => a.idx - b.idx);\n  return {\n    ready: ready.map(x => x.job.id),\n    waiting: waiting.map(x => ({id: x.it.job.id, at: x.at})),\n    dead: dead.map(x => x.job.id),\n    cancelled: cancelled.map(x => x.job.id),\n  };\n}\n"}}
```