<reasoning_content>
*   Task: Refactor a job queue into three specific classes: `Job`, `RetryPolicy`, and `JobQueue`.
    *    Requirements for `Job`: Dataclass with `id`, `data`, `priority`, and `retries`.
    *    Requirements for `RetryPolicy`: Handle retries with backoff (though "do NOT actually sleep" is specified). Return `(success, attempts_made)`.
    *    Requirements for `JobQueue`: Manage `Job` objects. `add_job`, `process_job` (using `RetryPolicy`), and `get_next_job` (highest priority first, then FIFO).
    *    Constraint: Only standard library. No explanations.

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

    *   `RetryPolicy`:
        *   `max_attempts` in `__init__`.
        *   `run(fn, data)` calls `fn(data)`.
        *   Logic: Loop up to `max_attempts`. Catch exceptions. If success, return `(True, attempts)`. If all fail, return `(False, max_attempts)`.
        *   Note: "Do NOT actually sleep".

    *   `JobQueue`:
        *   Needs a storage structure for jobs. To maintain priority (high value = high priority?) and FIFO, a priority queue or a sorted list is needed. Since `get_next_job` needs "highest-priority", and "FIFO order within the same priority", a `heapq` or a sorted list works.
        *   `add_job`: Create `Job` and store.
        *   `process_job`: Instantiate/use `RetryPolicy` to call `Processor`.
        *   `get_next_job`: Return `(id, data)` of the highest priority.

    *   *Priority:* Usually higher number means higher priority, but sometimes lower. I'll assume higher number = higher priority unless specified. Actually, standard heaps are min-heaps. To get highest priority first, I should store priority as a negative value if using `heapq`, or just use a list and sort/pop. Since `JobQueue` doesn't specify size/performance constraints, a list and sorting or a simple list that we search might be easiest, but a `heapq` is more "professional" for a queue.
    *   *FIFO order:* `heapq` doesn't guarantee FIFO for equal priorities unless there's a counter.
    *   *Data structures:* Let's use a list and keep it sorted, or a list and find the max. Given the "FIFO within same priority" requirement, keeping a list of jobs and selecting the max priority then first added is best.

    *Refining `JobQueue` structure:*
    A list of `Job` objects.
    `add_job`: append to list.
    `get_next_job`: find index of max priority. If multiple, pick lowest index (first in). Remove and return.

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

    *Refining `JobQueue` storage:*
    To satisfy "FIFO within same priority" correctly:
    When `get_next_job` is called:
    Find the max priority value in the list.
    Find the first index where `job.priority == max_priority`.
    Pop and return.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Tuple, Optional
    from collections import deque

    @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: dict) -> Tuple[bool, int]:
            attempts = 0
            while attempts < self.max_attempts:
                try:
                    fn(data)
                    return True, attempts + 1
                except Exception:
                    attempts += 1
            return False, attempts

    class JobQueue:
        def __init__(self):
            self.jobs: list[Job] = []

        def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
            self.jobs.append(Job(id=job_id, data=data, priority=priority))

        def process_job(self, job_id: str, processor: Callable) -> bool:
            # Find the job by ID
            job = next((j for j in self.jobs if j.id == job_id), None)
            if not job:
                return False
            
            policy = RetryPolicy()
            success, _ = policy.run(processor, job.data)
            return success

        def get_next_job(self) -> Optional[Tuple[str, dict]] -> None:
            if not self.jobs:
                return None
            
            # Find highest priority
            max_priority = max(j.priority for j in self.jobs)
            # Find first occurrence of that priority (FIFO)
            for i, job in enumerate(self.jobs):
                if job.priority == max_priority:
                    res = (job.id, job.data)
                    self.jobs.pop(i)
                    return res
            return None
    ```

    Wait, `get_next_job` signature `tuple[str, dict] | None`. `None` return type for a tuple is fine.

    "highest-priority job's (id, data); FIFO order within the same priority."
    Example:
    Job A (P: 1)
    Job B (P: 2)
    Job C (P: 2)
    Result should be B then C. My loop logic handles this.

    One detail: "process_job" ... "must USE RetryPolicy".
    In `process_job`, should I remove the job? The prompt doesn't explicitly say to remove it from the queue inside `process_job`, but usually, processing implies completion or moving to a failed state. However, `get_next_job` is the standard way to retrieve it. I will stick to the requirements.

    Wait, `Job` dataclass has `retries`. Does `RetryPolicy` update `retries`?
    Requirement 2: `run` returns `(success, attempts_made)`. It doesn't say to update the `Job` object's `retries` field.
    Requirement 3: `process_job` uses `RetryPolicy`.

    Let's double check the types.
    `RetryPolicy.run(self, fn: Callable, data) -> tuple[bool, int]`
    The prompt says `data` (no type) then `tuple[bool, int]`. I'll use `dict` or just leave it as is if preferred, but `Job.data` is `dict`.

    One more look at `JobQueue.process_job`:
    "must USE RetryPolicy (compose, don't reimplement retry logic)."
    Does `process_job` take the job from the queue? The prompt says `process_job(self, job_id: str, ...)`. It doesn't say to remove it. Usually, if `get_next_job` is available, `process_job` might be called on a job already retrieved, but the signature requires `job_id`. I will assume it finds the job in the internal storage.

    Wait, if `process_job` is called, does it stay in the queue? If `get_next_job` is used to "consume" the queue, then `process_job` might be redundant or used for specific jobs. I'll implement it to find the job by ID, execute the policy, and return the boolean result.

    *Self-correction on `get_next_job`:*
    If `get_next_job` is called, it returns the tuple. Usually, "get" implies "pop" in a queue context. If it doesn't pop, it's a "peek". Given "Queue", "get" usually implies "pop". I will pop it.

    Wait, the return type: `tuple[str, dict] | None`.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Tuple, Optional, List

    @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: dict) -> Tuple[bool, int]:
            attempts = 0
            while attempts < self.max_attempts:
                try:
                    fn(data)
                    return True, attempts + 1
                except Exception:
                    attempts += 1
            return False, attempts

    class JobQueue:
        def __init__(self):
            self.jobs: List[Job] = []

        def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
            self.jobs.append(Job(id=job_id, data=data, priority=priority))

        def process_job(self, job_id: str, processor: Callable) -> bool:
            job = next((j for j in self.jobs if j.id == job_id), None)
            if job is None:
                return False
            
            policy = RetryPolicy()
            success, _ = policy.run(processor, job.data)
            return success

        def get_next_job(self) -> Optional[Tuple[str, dict]] -> None:
            if not self.jobs:
                return None
            
            max_priority = max(j.priority for j in self.jobs)
            for i, job in enumerate(self.jobs):
                if job.priority == max_priority:
                    result = (job.id, job.data)
                    self.jobs.pop(i)
                    return result
            return None
    ```

    Final check:
    1. `Job`: dataclass, fields correct.
    2. `RetryPolicy`: `__init__` with `max_attempts`, `run` with logic, no sleep, returns tuple.
    3. `JobQueue`: `add_job`, `process_job` (uses `RetryPolicy`), `get_next_job` (highest priority, FIFO).

    One detail: `JobQueue.process_job` signature says `processor: Callable`.
    One detail: `RetryPolicy.run` signature says `fn: Callable, data`.

    Is there any requirement for "backoff"?
    "encapsulates retry-with-backoff".
    Usually, backoff means `time.sleep(2**attempts)`.
    However, the prompt says "Do NOT actually sleep".
    This means the logic for calculating the backoff time might be expected, but since we don't sleep, the loop is effectively just a repeated call. Or perhaps I should just skip the backoff calculation since sleeping is forbidden.

    Wait, `priority` - is higher number higher priority?
    In most systems (like RabbitMQ or Celery), a higher number is higher priority. I will assume higher number = higher priority.

    One more check: "Write only the Python code, no explanations."

    Wait, one tiny detail: `JobQueue.get_next_job` return type `tuple[str, dict] | None`.
    In Python 3.10+, `|` is valid. In older, it's `Optional`. The prompt uses `|` in the description, so I will use that style or standard types. Actually, I'll use the exact types suggested in the prompt text for signatures where applicable.

    The prompt says `tuple[bool, int]` and `tuple[str, dict] | None`. These are 3.10+ style. I will use them.

    ```python
    from dataclasses import dataclass
    from typing import Callable

    @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: dict) -> tuple[bool, int]:
            attempts = 0
            while attempts < self.max_attempts:
                try:
                    fn(data)
                    return True, attempts + 1
                except Exception:
                    attempts += 1
            return False, attempts

    class JobQueue:
        def __init__(self):
            self.jobs = []

        def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
            self.jobs.append(Job(id=job_id, data=data, priority=priority))

        def process_job(self, job_id: str, processor: Callable) -> bool:
            job = next((j for j in self.jobs if j.id == job_id), None)
            if not job:
                return False
            
            policy = RetryPolicy()
            success, _ = policy.run(processor, job.data)
            return success

        def get_next_job(self) -> tuple[str, dict] | None:
            if not self.jobs:
                return None
            
            max_priority = max(j.priority for j in self.jobs)
            for i, job in enumerate(self.jobs):
                if job.priority == max_priority:
                    result = (job.id, job.data)
                    self.jobs.pop(i)
                    return result
            return None
    ```

    Wait, one last look: `RetryPolicy.run(self, fn: Callable, data)`. The prompt doesn't have a type for `data`. I'll use `dict` to be safe since `Job.data` is `dict`.

    Double check: `JobQueue.process_job` uses `RetryPolicy`. Done.
    Double check: `JobQueue.get_next_job` is FIFO for same priority. Done (the first one in the list with the max priority is the oldest one added).

    Final check on `RetryPolicy`: "Do NOT actually sleep".
    If I don't sleep, there is no "backoff" implementation visible. I'll just leave it as a simple retry loop.

    Actually, looking at "retry-with-backoff", sometimes people want to see the backoff logic (like `delay = 2**attempts`) even if not used. But since it's "do not sleep", I'll keep it simple.

    Final check on `JobQueue.get_next_job`:
    Does "get" imply removal?
    "return the highest-priority job's (id, data)".
    In a "Queue", `get` or `dequeue` usually removes. I will proceed with popping.
</reasoning_content>

<content>
```python
from dataclasses import dataclass
from typing import Callable

@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: dict) -> tuple[bool, int]:
        attempts = 0
        while attempts < self.max_attempts:
            try:
                fn(data)
                return True, attempts + 1
            except Exception:
                attempts += 1
        return False, attempts

class JobQueue:
    def __init__(self):
        self.jobs = []

    def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
        self.jobs.append(Job(id=job_id, data=data, priority=priority))

    def process_job(self, job_id: str, processor: Callable) -> bool:
        job = next((j for j in self.jobs if j.id == job_id), None)
        if job is None:
            return False
        
        policy = RetryPolicy()
        success, _ = policy.run(processor, job.data)
        return success

    def get_next_job(self) -> tuple[str, dict] | None:
        if not self.jobs:
            return None
        
        max_priority = max(j.priority for j in self.jobs)
        for i, job in enumerate(self.jobs):
            if job.priority == max_priority:
                result = (job.id, job.data)
                self.jobs.pop(i)
                return result
        return None
```
</content>