<reasoning_content>
*   Goal: Refactor a job queue into three composable classes (`Job`, `RetryPolicy`, `JobQueue`) in `solution.py`.
    *   Constraints:
        *   `Job`: Dataclass with `id` (str), `data` (dict), `priority` (int, default 0), `retries` (int, default 0).
        *   `RetryPolicy`:
            *   `__init__` with `max_attempts` (int, default 4).
            *   `run(self, fn: Callable, data) -> tuple[bool, int]`: Execute `fn(data)`, retry on exception. No `sleep`. Return `(success, count)`.
        *   `JobQueue`:
            *   `add_job(self, job_id: str, data: dict, priority: int = 0) -> None`: Add to queue.
            *   `process_job(self, job_id: str, processor: Callable) -> bool`: Use `RetryPolicy`.
            *   `get_next_job(self) -> tuple[str, dict] | None`: Return highest priority (FIFO within priority).
        *   Library: Standard library only.
        *   Output: Only Python code.

    *   `Job`: `from dataclasses import dataclass`.
    *   `RetryPolicy`: Needs `Callable` from `typing`. Needs a loop to handle retries.
    *   `JobQueue`: Needs a way to store jobs sorted by priority. A list sorted by priority and then order of insertion (FIFO) works well. Since I need to find specific IDs for `process_job`, I should probably store them in a way that's easy to find by ID, but `get_next_job` needs priority.
        *   *Self-correction:* `get_next_job` needs the "highest priority". Usually, higher number means higher priority, or lower? Usually, priority queues (like `heapq`) work with lowest number = highest priority, but standard queues often use higher numbers. Let's assume higher numbers = higher priority or simply sort descending. Actually, standard PriorityQueue logic is usually lowest value first. But "highest-priority" usually implies the highest value in a priority field unless specified. I'll use a list and sort it or use `heapq`. Let's use a simple list and sort/pop or a `heapq` with a counter for FIFO.

    *Refining JobQueue structure:*
    To handle `process_job(job_id)`, I need to find the job by ID.
    To handle `get_next_job`, I need to find the highest priority.
    Data structure: A list of `Job` objects.
    `add_job`: Append to list.
    `get_next_job`: Sort by `-priority` (descending) then by insertion order.
    `process_job`: Find job by id, run via `RetryPolicy`.

    *Wait*, `process_job` takes `job_id`. If I remove it or modify it? The prompt doesn't say to remove it, but usually, processing happens. However, `get_next_job` returns `(id, data)`.

    Let's look at the requirements again.
    `JobQueue` methods:
    1. `add_job(job_id, data, priority)`
    2. `process_job(job_id, processor)`
    3. `get_next_job()`

    I'll use a dictionary to store jobs by ID for $O(1)$ lookup in `process_job`, and a list to store the order/priority for `get_next_job`. Or just a list and iterate. Since no scale is specified, a list is fine.

    Wait, `RetryPolicy` signature: `run(self, fn: Callable, data) -> tuple[bool, int]`.
    It takes `data`, not the `Job` object.

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

    `RetryPolicy`:
    ```python
    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
    ```

    `JobQueue`:
    ```python
    class JobQueue:
        def __init__(self):
            self.jobs = [] # List of Job objects
            self.retry_policy = RetryPolicy()

        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
            success, _ = self.retry_policy.run(processor, job.data)
            return success

        def get_next_job(self) -> tuple[str, dict] | None:
            if not self.jobs:
                return None
            # Sort by priority descending, then FIFO (implicit by list order)
            # We want highest priority (max value).
            # list.sort() is stable.
            sorted_jobs = sorted(self.jobs, key=lambda x: x.priority, reverse=True)
            return sorted_jobs[0].id, sorted_jobs[0].data
    ```

    *Refinement on `get_next_job`:*
    "return the highest-priority job's (id, data); FIFO order within the same priority."
    If I use `sorted(..., key=lambda x: x.priority, reverse=True)`, it is stable. So jobs with same priority stay in their original insertion order.

    *Refinement on `process_job`:*
    The prompt says "must USE RetryPolicy (compose, don't reimplement retry logic)".
    My implementation does this.

    *Imports:*
    `from dataclasses import dataclass`
    `from typing import Callable`

    *Wait*, `RetryPolicy` `run` signature says `run(self, fn: Callable, data)`.
    Does it mean `data` or `Job`? Prompt says `data`.
    Actually, `data` is a `dict`.

    *Logic Check:*
    - `Job` dataclass? Yes.
    - `RetryPolicy` max_attempts? Yes.
    - `RetryPolicy` run retries on exception? Yes.
    - `RetryPolicy` no sleep? Yes.
    - `RetryPolicy` returns `(bool, int)`? Yes.
    - `JobQueue` `add_job`? Yes.
    - `JobQueue` `process_job` uses `RetryPolicy`? Yes.
    - `JobQueue` `get_next_job` highest priority/FIFO? Yes.
    - Only standard library? Yes.

    Let's double check `get_next_job`. If multiple jobs have same priority, I need FIFO.
    Example:
    Job A: priority 10, index 0
    Job B: priority 10, index 1
    Job C: priority 20, index 2
    Sorted descending:
    Job C (20)
    Job A (10)
    Job B (10)
    This is correct.

    Wait, should `process_job` remove the job? The prompt doesn't say. It just says process it. I'll leave it in the list.

    *Typing:*
    The prompt uses `tuple[bool, int]` and `tuple[str, dict] | None`. This is Python 3.10+ syntax. I will ensure the code is compatible or use standard types if necessary, but usually, these prompts expect modern syntax.

 to be strictly compliant with "no explanations" and "only Python code".

    ```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: list[Job] = []
            self.retry_policy = RetryPolicy()

        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
            success, _ = self.retry_policy.run(processor, job.data)
            return success

        def get_next_job(self) -> tuple[str, dict] | None:
            if not self.jobs:
                return None
            # Sorting is stable in Python, so FIFO is preserved for same priority
            # We want highest priority first (desc order)
            top_job = max(self.jobs, key=lambda x: x.priority)
            # Wait, max() doesn't guarantee FIFO for ties? 
            # Actually, max() returns the first occurrence of the maximum value. 
            # So max() is sufficient and more efficient than sorting.
            return top_job.id, top_job.data
    ```
    Wait, let's re-verify `max()`.
    `max([Job(id='a', priority=10), Job(id='b', priority=10)], key=lambda x: x.priority)`
    In Python, `max` returns the *first* item encountered that has the maximum value.
    Since `self.jobs` is an ordered list of insertions, the first occurrence is the oldest one (FIFO).
    So `max()` is perfect.

    One tiny thing: `Job` definition: `priority: int = 0`, `retries: int = 0`.
    The `RetryPolicy` `run` method doesn't actually update `job.retries`. The prompt doesn't ask to update it, only to use `RetryPolicy` to run the function.

    Check return type of `process_job`: `bool`. Yes.
    Check return type of `get_next_job`: `tuple[str, dict] | None`. Yes.

    One detail: `RetryPolicy.run` signature: `run(self, fn: Callable, data)`.
    Is `data` supposed to be `dict` or `Any`? The prompt says `data: dict` in `Job` and `data` in `run`. I'll stick to `dict`.

    Final review of classes:
    1. `Job`: `id`, `data`, `priority`, `retries`. Done.
    2. `RetryPolicy`: `__init__`, `run`. Done.
    3. `JobQueue`: `add_job`, `process_job`, `get_next_job`. Done.

    Wait, `process_job` signature: `process_job(self, job_id: str, processor: Callable) -> bool`.
    Does `processor` take `data`? Yes, `fn(data)`.

    ```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: list[Job] = []
            self.retry_policy = RetryPolicy()

        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
            success, _ = self.retry_policy.run(processor, job.data)
            return success

        def get_next_job(self) -> tuple[str, dict] | None:
            if not self.jobs:
                return None
            # max() returns the first occurrence of the maximum value,
            # which preserves FIFO for identical priorities.
            best_job = max(self.jobs, key=lambda j: j.priority)
            return best_job.id, best_job.data
    ```

    Double check `Job` signature: `priority: int = 0`, `retries: int = 0`.
    Done.

    One more check: "Do NOT actually sleep" in `RetryPolicy`.
    Done (no time.sleep).

    One more check: "return the highest-priority job's (id, data)".
    Done.

    One more check: "compose, don't reimplement retry logic".
    Done. `JobQueue.process_job` calls `self.retry_policy.run`.
</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: list[Job] = []
        self.retry_policy = RetryPolicy()

    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
        
        success, _ = self.retry_policy.run(processor, job.data)
        return success

    def get_next_job(self) -> tuple[str, dict] | None:
        if not self.jobs:
            return None
        
        # max() returns the first occurrence of the maximum value,
        # which satisfies the FIFO requirement for equal priorities.
        best_job = max(self.jobs, key=lambda j: j.priority)
        return best_job.id, best_job.data
```
</content>