<reasoning_content>
*   Refactor a job queue into three specific classes: `Job`, `RetryPolicy`, and `JobQueue`.
    *   `Job`: Dataclass (`id`, `data`, `priority`, `retries`).
    *   `RetryPolicy`: Handles retry logic with backoff (conceptually, though the prompt says "Do NOT actually sleep").
        *   `run(fn, data)` returns `(success, attempts_made)`.
    *   `JobQueue`: Manages jobs using a priority queue.
        *   `add_job(job_id, data, priority)`
        *   `process_job(job_id, processor)`: Uses `RetryPolicy`.
        *   `get_next_job()`: Returns highest priority (FIFO for same priority).
    *   Constraints: Standard library only, no external packages, no explanations, write only the code.

    *   `id: str`
    *   `data: dict`
    *   `priority: int = 0`
    *   `retries: int = 0` (Note: `retries` might be used to track attempts, but the prompt specifically says `RetryPolicy` handles the attempts in `run`. However, `Job` has a `retries` field, so I'll include it as requested).

    *   `max_attempts: int = 4`
    *   `run(fn, data)`:
        *   Loop up to `max_attempts`.
        *   Try `fn(data)`.
        *   Catch exception.
        *   Return `(True, attempts)` if successful.
        *   Return `(False, attempts)` if failed all attempts.
        *   "Do NOT actually sleep".

    *   `add_job`: Store in a way that supports priority and FIFO.
        *   Python's `heapq` is the standard way to implement a priority queue.
        *   To handle FIFO within the same priority, we can use a counter (sequence number).
        *   `heapq` is a min-heap. The prompt says "highest-priority". Usually, in priority queues, a lower number means higher priority, OR a higher number means higher priority. In standard CS, higher number = higher priority, but `heapq` is a min-heap. I'll assume "highest-priority" means the user provides a number where higher = more important, but to work with `heapq` easily, I'll store priority as `-priority`.
        *   Wait, let's look at common conventions. Usually, `priority=0` is low, `priority=10` is high. If I use `heapq`, I'll store `(-priority, sequence_number, job)`.
    *   `process_job(job_id, processor)`:
        *   Find the job in the queue? The prompt says `process_job(job_id, processor)`. This implies we look up a job by its ID.
        *   However, `get_next_job` returns the highest-priority job.
        *   `process_job` needs to use `RetryPolicy`.
        *   Wait, if `process_job` takes a `job_id`, where does it get the job? It must be stored in an internal dictionary or the queue itself.
        *   If I use `heapq`, finding a specific `job_id` is $O(N)$. If I use a dict for storage and a heap for ordering, I can manage it.
    *   `get_next_job`: Returns `(id, data)`.

    *   `JobQueue` needs to store jobs.
    *   `self.jobs: dict[str, Job]` for lookups.
    *   `self.queue: list` (for `heapq`) containing `(-priority, sequence_number, job_id)`.
    *   `self.counter: int` to ensure FIFO.
    *   `self.retry_policy = RetryPolicy()` (The prompt says `JobQueue` orchestrates using `RetryPolicy`, so it should probably have an instance or create one).

    *   `add_job`:
        *   `job = Job(id=job_id, data=data, priority=priority)`
        *   `self.jobs[job_id] = job`
        *   `heapq.heappush(self.queue, (-priority, self.counter, job_id))`
        *   `self.counter += 1`
    *   `get_next_job`:
        *   While `self.queue`:
            *   `p, seq, jid = heapq.heappop(self.queue)`
            *   If `jid` is in `self.jobs`:
                *   `job = self.jobs[jid]`
                *   return `(job.id, job.data)`
        *   Return `None`.
    *   `process_job`:
        *   `job = self.jobs.get(job_id)`
        *   If not `job`, return `False`.
        *   `policy = RetryPolicy()` (Or instance of it).
        *   `success, attempts = policy.run(processor, job.data)`
        *   If not `success`:
            *   `job.retries = attempts` (Or however we update it).
            *   Return `False`.
        *   Else:
            *   `del self.jobs[job_id]` (assuming processing means removal).
            *   Return `True`.

    *Correction on `process_job`:* If `process_job` removes the job on success, `get_next_job` needs to handle the fact that `heappop` might return a `job_id` that was already removed from `self.jobs`.

    *Refining `process_job`:* The prompt says `process_job` must use `RetryPolicy`. It doesn't explicitly say to remove the job from the queue, but in a typical job queue, processing a job implies completion. I'll assume it removes it on success.

    *   `Job`: `dataclass`? Yes.
    *   `RetryPolicy`: `run` returns `(bool, int)`? Yes. No sleep? Yes.
    *   `JobQueue`:
        *   `add_job` signature correct? Yes.
        *   `process_job` signature correct? Yes. Uses `RetryPolicy`? Yes.
        *   `get_next_job` returns `(id, data) | None`? Yes. FIFO for same priority? Yes.

    *Wait, priority ordering:*
    If priority 10 is "higher" than priority 0:
    `heapq` is a min-heap.
    To get the highest priority first, we store `-priority`.
    Example: P10, P0, P10.
    Heap: `[(-10, 0, id1), (-10, 2, id3), (0, 1, id2)]`.
    This gives id1 (P10), then id3 (P10), then id2 (P0). This matches "Highest priority, then FIFO".

    ```python
    from dataclasses import dataclass
    from typing import Callable, Any
    import heapq

    @dataclass
    class Job:
        id: str
        data: dict
        priority: int = 0
        retries: int = 0

    class RetryPolicy:
        def __init__(self, max_attempts: int = 4):
            self.max_attempts = max_attempts

        def run(self, fn: Callable, data: Any) -> tuple[bool, int]:
            attempts = 0
            while attempts < self.max_attempts:
                attempts += 1
                try:
                    fn(data)
                    return True, attempts
                except Exception:
                    continue
            return False, attempts

    class JobQueue:
        def __init__(self):
            self.jobs: dict[str, Job] = {}
            self.heap: list[tuple[int, int, str]] = []
            self.counter = 0
            self.retry_policy = RetryPolicy()

        def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
            job = Job(id=job_id, data=data, priority=priority)
            self.jobs[job_id] = job
            heapq.heappush(self.heap, (-priority, self.counter, job_id))
            self.counter += 1

        def process_job(self, job_id: str, processor: Callable) -> bool:
            job = self.jobs.get(job_id)
            if not job:
                return False
            
            success, attempts = self.retry_policy.run(processor, job.data)
            if success:
                del self.jobs[job_id]
                return True
            else:
                job.retries = attempts
                return False

        def get_next_job(self) -> tuple[str, dict] | None:
            while self.heap:
                _, _, job_id = heapq.heappop(self.heap)
                if job_id in self.jobs:
                    job = self.jobs[job_id]
                    return (job.id, job.data)
            return None
    ```

    *   `RetryPolicy` is a class. The prompt says `RetryPolicy` encapsulates retry-with-backoff. My `run` doesn't actually have backoff (sleep), but the prompt says "Do NOT actually sleep". So "backoff" is essentially a placeholder for the logic pattern.
    *   `JobQueue`: `process_job` must use `RetryPolicy`. I've instantiated it in `__init__`.
    *   `get_next_job` signature: `tuple[str, dict] | None`.
    *   Imports: `dataclasses`, `typing`, `heapq`.

    *One detail:* `process_job` in my implementation removes the job from `self.jobs`. If `get_next_job` is called after a successful `process_job`, the `heappop` will find the `job_id` is no longer in `self.jobs` and continue to the next one. This is correct.

    *Wait, priority direction:* If `priority` is 0 (low) and 10 (high), `-10` is smaller than `0`. So `heapq` will pop `-10` first. This is correct for a min-heap to act like a max-priority queue.

    *One edge case:* What if `process_job` is called on a `job_id` that is still in the heap but `self.jobs` has been updated? `process_job` takes a `job_id`. If it succeeds, it removes the job from `self.jobs`. The heap will still contain that `job_id`, but `get_next_job` checks `if job_id in self.jobs`. This is robust.

    *Another edge case:* `process_job` failure. The prompt says `retries` field is in `Job`. I'll update it.

    *Refining `process_job` logic*: The user didn't specify if `process_job` should remove the job on failure. Usually, you'd leave it or re-queue it. But `process_job` is for a *specific* `job_id`. The requirement is `process_job(job_id, processor)`. If it fails, I'll just update `retries` and return `False`.

    Let's double check `RetryPolicy.run`.
    `run(self, fn: Callable, data) -> tuple[bool, int]`
    `attempts_made` is the second element.
    In my code:
    ```python
    attempts = 0
    while attempts < self.max_attempts:
        attempts += 1
        try:
            fn(data)
            return True, attempts
        except Exception:
            pass
    return False, attempts
    ```
    If `max_attempts` is 4.
    Loop 1: `attempts`=1. If fail...
    Loop 2: `attempts`=2. If fail...
    Loop 3: `attempts`=3. If fail...
    Loop 4: `attempts`=4. If fail, exit loop.
    Return `False, 4`. Correct.

    Final check on `JobQueue.get_next_job`:
    "return the highest-priority job's (id, data); FIFO order within the same priority."
    My heap stores `(-priority, self.counter, job_id)`.
    If priorities are 10 and 10.
    Entries: `(-10, 0, id1)`, `(-10, 1, id2)`.
    `heapq.heappop` will return `(-10, 0, id1)` first. Correct.

    Final code structure:
    - Import `dataclass`, `Callable`, `Any`, `heapq`.
    - `Job` class.
    - `RetryPolicy` class.
    - `JobQueue` class.

    One small detail: `data` in `run` is `Any` (though prompt says `data` in `Job` is `dict`). I'll use `Any` for the type hint in `run` for flexibility.

    *Self-Correction on `process_job`*: If `process_job` is called, should it remove the job from the queue if it's successful? Yes, that's how a job queue works. If it's not successful, the job stays in the `jobs` dict (with updated `retries`). The heap will still point to it. The next time `get_next_job` is called, it will return the job again (since it's still in `self.jobs`). This allows for re-processing.

    *Wait*, if the job stays in `self.jobs` and is in the heap, and `process_job` fails, `get_next_job` will just return the same job again. This is correct behavior for a retryable system.

    *Wait*, what if `process_job` is called, fails, and then `get_next_job` is called? `get_next_job` returns the job. Then `process_job` is called on that job again. This loop is fine.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Any
    import heapq

    @dataclass
    class Job:
        id: str
        data: dict
        priority: int = 0
        retries: int = 0

    class RetryPolicy:
        def __init__(self, max_attempts: int = 4):
            self.max_attempts = max_attempts

        def run(self, fn: Callable, data: Any) -> tuple[bool, int]:
            attempts = 0
            while attempts < self.max_attempts:
                attempts += 1
                try:
                    fn(data)
                    return True, attempts
                except Exception:
                    continue
            return False, attempts

    class JobQueue:
        def __init__(self):
            self._jobs: dict[str, Job] = {}
            self._heap: list[tuple[int, int, str]] = []
            self._counter = 0
            self._retry_policy = RetryPolicy()

        def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
            job = Job(id=job_id, data=data, priority=priority)
            self._jobs[job_id] = job
            heapq.heappush(self._heap, (-priority, self._counter, job_id))
            self._counter += 1

        def process_job(self, job_id: str, processor: Callable) -> bool:
            job = self._jobs.get(job_id)
            if not job:
                return False
            
            success, attempts = self._retry_policy.run(processor, job.data)
            if success:
                del self._jobs[job_id]
                return True
            else:
                job.retries = attempts
                return False

        def get_next_job(self) -> tuple[str, dict] | None:
            while self._heap:
                _, _, job_id = heapq.heappop(self._heap)
                if job_id in self._jobs:
                    job = self._jobs[job_id]
                    return (job.id, job.data)
            return None

    ```

    Is `data` in `run` a dict? Yes, `Job.data` is `dict`.
    The signature of `process_job` is `process_job(self, job_id: str, processor: Callable) -> bool`.
    The signature of `get_next_job` is `get_next_job(self) -> tuple[str, dict] | None`.
    Everything seems solid.
</reasoning_content>

<content>
```python
from dataclasses import dataclass
from typing import Callable, Any
import heapq

@dataclass
class Job:
    id: str
    data: dict
    priority: int = 0
    retries: int = 0

class RetryPolicy:
    def __init__(self, max_attempts: int = 4):
        self.max_attempts = max_attempts

    def run(self, fn: Callable, data: Any) -> tuple[bool, int]:
        attempts = 0
        while attempts < self.max_attempts:
            attempts += 1
            try:
                fn(data)
                return True, attempts
            except Exception:
                continue
        return False, attempts

class JobQueue:
    def __init__(self):
        self._jobs: dict[str, Job] = {}
        self._heap: list[tuple[int, int, str]] = []
        self._counter = 0
        self._retry_policy = RetryPolicy()

    def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
        job = Job(id=job_id, data=data, priority=priority)
        self._jobs[job_id] = job
        # Use -priority for max-heap behavior with heapq (min-heap)
        # Use self._counter to ensure FIFO for same priority
        heapq.heappush(self._heap, (-priority, self._counter, job_id))
        self._counter += 1

    def process_job(self, job_id: str, processor: Callable) -> bool:
        job = self._jobs.get(job_id)
        if not job:
            return False
        
        success, attempts = self._retry_policy.run(processor, job.data)
        if success:
            del self._jobs[job_id]
            return True
        else:
            job.retries = attempts
            return False

    def get_next_job(self) -> tuple[str, dict] | None:
        while self._heap:
            _, _, job_id = heapq.heappop(self._heap)
            if job_id in self._jobs:
                job = self._jobs[job_id]
                return (job.id, job.data)
        return None
```
</content>