# Claiming work — spec (candidate copy)

We run an annotation platform. Work enters as **tasks**; each task moves through an ordered list of **steps**
(e.g. `author → review → qa`). Each step of each task is a **node**. A human **claims** a node to work on it.

## Claiming
- A node is `parked` (nobody on it) or `running` (claimed). `claim(node, user)`:
  - the task must be `open` (not paused/done)
  - a `parked` node becomes `running`, assigned to the user, `claimed_at = now`
  - a `running` node claimed by **the same user** is a *resume* — no change, no error
  - a `running` node claimed by **someone else** is a **Conflict**
- **Seats.** A user may only claim a node whose step is in their seats for that project.
- **Four-eyes.** On a *review* step (`review`, `qa`), the user must be different from **every** person who
  acted on **any earlier node of the same task**. The author cannot review their own work, and the reviewer
  cannot also be the QA.
- **Idle unclaim.** A `running` node whose `claimed_at` is older than the project's `idle_hours` counts as
  `parked` again: anyone eligible may claim it (this replaces the previous assignee), and it no longer
  appears in the previous assignee's "waiting on you".

## Drafts
`save_draft(node, user, content, expected_revision)` — only the current assignee may save. The node carries a
`revision` counter; the client sends the revision it loaded. A save whose `expected_revision` is not the
current revision is **Stale** (409) — nothing is written. A successful save stores the content and bumps
the revision by one.

## Worklist
`worklist(user, now)` returns two **disjoint** lists:
- `waiting_on_you` — nodes running under this user that are not idle-expired
- `available` — nodes this user could claim right now (parked or idle-expired, task open, seat held,
  four-eyes satisfied)

---
`claims.py` implements this. `test_claims.py` encodes the spec. Some tests fail. Find out why, fix the code
(not the tests), and tell us what you changed and why.
