I implemented `evaluate` under the CURRENT policy. Nothing was run or tested.

Choices I made where the spec left room:

- **Dedup:** I iterate over all jobs and keep the last job per `key`. Each kept job retains its original index. Survivors are processed in original-index order.
- **Not retryable:** `last_code` is not in `retryable_codes`. A zero `last_code` is therefore not retryable, and it means no failure.
- **Failure:** `last_code` is nonzero. A retryable job with `last_code` 0 can't occur, so `failed_at` only matters for retryable jobs.
- **Cancelled:** with `cancel_wins` false, a job goes to `cancelled` only if it is cancelled and not retryable. A cancelled but retryable job continues through the remaining checks.
- **Dead:** a job is dead if `now - created > stale_after` (strict, since `stale_inclusive` is false), or `attempt >= max_attempts`, or `last_code` is nonzero and not retryable.
- **Due:** for a retryable job, `due = max(due, failed_at + min(base_delay * 1, delay_cap))`, because `exponential` is false. The `exponential` branch is still implemented for completeness.
- **Waiting and ready:** a job with `due > now` goes to `waiting` as `{id, at: due}`. Otherwise it goes to `ready`.
- **Ready sort:** priority ascending (`priority_high_first` is false), then `created` descending (`fifo_ties` is false), then original index.
- **Dead order:** `dead_order` is "input", so dead jobs are sorted by original index. Waiting and cancelled are also in original-index order.

```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 cmp(a: number, b: number): number { return a < b ? -1 : a > b ? 1 : 0; }\n", "core.ts": "import {cmp} from './helpers.ts';\n\nexport function run(payload: any, policy: any): any {\n  const now: number = payload.now;\n  const jobs: any[] = payload.jobs;\n  const last = new Map<any, number>();\n  jobs.forEach((j, i) => { last.set(j[policy.dedup_key], i); });\n  const idxs = Array.from(last.values()).sort((a, b) => a - b);\n  const ready: any[] = [];\n  const waiting: any[] = [];\n  const dead: any[] = [];\n  const cancelled: any[] = [];\n  for (const i of idxs) {\n    const j = jobs[i];\n    const retryable = policy.retryable_codes.includes(j.last_code);\n    if (j.cancelled && (policy.cancel_wins || !retryable)) {\n      cancelled.push({j, i});\n      continue;\n    }\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)) {\n      dead.push({j, i});\n      continue;\n    }\n    let due = j.due;\n    if (retryable && j.last_code !== 0) {\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({j, i, at: due});\n    else ready.push({j, i});\n  }\n  ready.sort((a, b) => {\n    let c = cmp(a.j.priority, b.j.priority);\n    if (policy.priority_high_first) c = -c;\n    if (c) return c;\n    c = cmp(a.j.created, b.j.created);\n    if (!policy.fifo_ties) c = -c;\n    if (c) return c;\n    return a.i - b.i;\n  });\n  if (policy.dead_order === 'id') {\n    dead.sort((a, b) => cmp(a.j.id, b.j.id) || a.i - b.i);\n  }\n  return {\n    ready: ready.map((x) => x.j.id),\n    waiting: waiting.map((x) => ({id: x.j.id, at: x.at})),\n    dead: dead.map((x) => x.j.id),\n    cancelled: cancelled.map((x) => x.j.id),\n  };\n}\n"}}
```