<reasoning_content>
*   Input: A task description asking to refactor a job queue into three specific classes: `Job`, `RetryPolicy`, and `JobQueue`.
    *   Constraints:
        *   Use `dataclasses`.
        *   `RetryPolicy` handles retries with backoff logic (logic provided, but no actual `sleep`).
        *   `JobQueue` manages the jobs and uses `RetryPolicy`.
        *   `JobQueue.get_next_job` must return highest priority first (FIFO within priority).
        *   Use only the standard library.
        *   Output format: Python code only, no explanations.

    *   **Class 1: `Job`**
        *   Fields: `id: str`, `data: dict`, `priority: int = 0`, `retries: int = 0`.
        *   Decorator: `@dataclass`.

    *   **Class 2: `RetryPolicy`**
        *   `__init__(self, max_attempts: int = 4)`
        *   `run(self, fn: Callable, data: dict) -> tuple[bool, int]`
        *   Logic: Try calling `fn(data)`. If exception, retry. Max attempts. Return `(success, count)`. Note: "Do NOT actually sleep".
        *   *Wait*, "retries on any exception". Should it catch *all* exceptions? Yes, usually.

    *   **Class 3: `JobQueue`**
        *   `add_job(self, job_id: str, data: dict, priority: int = 0) -> None`
        *   `process_job(self, job_id: str, Processor: Callable) -> bool`
        *   `get_next_job(self) -> tuple[str, dict] | None`
        *   Storage: Needs a list or a priority queue. Since I need FIFO within same priority, a list sorted by priority (descending) and insertion order works well. Or a `heapq` (but `heapq` is min-heap, so priority would need negation).
        *   Requirement: `process_job` must use `RetryPolicy`.

    *   *Imports*: `dataclasses`, `typing.Callable`, `typing.Tuple`, `typing.Dict`, `typing.List`, `typing.Optional`.

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

    *   *RetryPolicy Class*:
        ```python
        class RetryPolicy:
            def __init__(self, max_attempts: int = 4):
                self.max_attempts = max_attempts

            def run(self, fn: Callable[[dict]], 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 Class*:
        *   Storage: `self._jobs: List[Job]`
        *   `add_job`: Create `Job`, append to list.
        *   `get_next_job`: Sort by priority (highest first), then take first. To keep it FIFO, the insertion order matters.
        *   Actually, a better way to handle FIFO within priority: Keep the list ordered. When adding a job, insert it in the right spot or sort every time. Since the queue isn't expected to be massive, sorting on `get_next_job` or maintaining a sorted list is fine.
        *   Wait, `get_next_job` says "FIFO order within the same priority".
        *   If I sort by `-priority` and then by order of arrival, I get it.
        *   Let's store them in a list. `add_job` appends. `get_next_job` finds the max priority, then the first occurrence.

        *   *Revised logic for `JobQueue`*:
            ```python
            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[[dict]]) -> bool:
                    job = next((j for j in self._jobs if j.id == job_id), None)
                    if not job:
                        return False
                    success, _ = self._retry_ency.run(Processor, job.data)
                    return success

                def get_next_job(self) -> tuple[str, dict]] | None:
                    if not self._jobs:
                        return None
                    # Sort by priority desc, then keep original order (stable sort)
                    # Actually, just find max priority and take first.
                    sorted_jobs = sorted(self._jobs, key=lambda x: x.priority, reverse=True)
                    best_job = sorted_jobs[0]
                    return (best_job.id, best_job.data)
            ```

    *   *Correction on `process_job`*: The prompt says "must USE RetryPolicy".
    *   * Correction on `get_next_job`*: If I remove the job from the list after getting it? The prompt doesn't say "pop", but usually a "get_next_job" implies taking it out or at least identifies it. However, the prompt says "return the highest-priority job's (id, data)". I will assume it shouldn't remove it unless asked, but usually, queues consume. I'll check the wording: "return". I'll return it but not remove it to stay safe with the prompt's phrasing, or I'll remove it if standard queue behavior is implied. Actually, usually "get" doesn't remove, "pop" does. But since it's a "Queue", I'll assume it stays in the list unless consumed. Wait, if I don't remove it, `get_next_job` always returns the same one. That's a bug for a queue. I will remove it.

    *   *Refining `JobQueue`*:
        To ensure FIFO within same priority:
        `sorted(self._jobs, key=lambda x: x.priority, reverse=True)` is a stable sort in Python. So the first one in the list among ties will be first.
        To remove it: `self._jobs.remove(best_job)`.

    *   *Wait*, the prompt says "return the highest-priority job's (id, data)". It doesn't say "remove". But a queue that doesn't remove is not a queue. I'll implement it so it returns the tuple. If the user wants to process it, they call `process_job`. Wait, `process_job` takes an ID. So `get_next_job` identifies it. I'll keep it simple: `get_next_job` finds and returns.

    *   Let's look at `process_job` again. `process_job(self, job_id: str, Processor: Callable)`. It doesn't remove the job. This suggests the `JobQueue` is a container where you can look up jobs by ID or find the next one.

    *   Actually, let's look at `get_next_job` again. "return the highest-priority job's (id, data)".
    If I don't remove it, `get_next_job` will always return the same one.
    Let's look at the requirements again.
    `process_job` takes `job_id`.
    So the flow is:
    1. `add_job`
    2. `get_next_job` -> gives me ID
    3. `process_job` -> processes it.
    If `process_job` doesn't remove it, it stays there. If `get_next_job` doesn't remove it, it stays there.
    I'll make `get_next_job` return it without removal, as the prompt is specific about the return type and doesn't mention "remove".

    *   *Actually*, "FIFO order within the same priority". If I don't remove it, and I call `get_next_job` twice, it returns the same one. That's fine for a "get" method.

    *   *RetryPolicy implementation details*:
        "returns (success, attempts_made)".
        The prompt says "retries on any exception until success or max_attempts reached".
        So if it succeeds on attempt 1, `attempts_made` is 1.
        If it fails 3 times and succeeds on 4th, `attempts_made` is 4.
        If it fails 4 times, `attempts_made` is 4.

    *   *Final structure check*:
        `Job` dataclass.
        `RetryPolicy` with `run`.
        `JobQueue` with `add_job`, `process_job`, `get_next_job`.

    *   *Wait*, `get_next_job` needs to handle the "FIFO order within the same priority".
        Since `add_job` appends to a list, the list maintains insertion order.
        A stable sort or just finding the max priority and then the first index of that max.

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

    @dataclass
    class Job:
        id: str
        data: dict
instances
        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[[dict]], data: dict) -> 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: 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[[dict]]) -> 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) -> Optional[Tuple[str, dict]]
            if not self._jobs:
                return None
            # Find max priority
            max_priority = max(j.priority for j in self._jobs)
            # Find first job with that priority (FIFO)
            for j in self._jobs:
                if j.priority == max_priority:
                    return (j.id, j.data)
            return None
    ```

    Wait, let's re-read: `process_job(self, job_id: str, Processor: Callable) -> bool`.
    If I don't remove the job, it stays in the queue. Is that correct?
    The prompt doesn't say "pop". Usually, in these coding tasks, if they don't say "remove", don't remove.
    However, `get_next_job` returning the highest priority job *while others exist* makes sense.

    One detail: `RetryPolicy.run` "retries on any exception".
    If `fn(data)` succeeds, it returns `(True, attempts_made)`.
    If it fails `max_attempts` times, it returns `(False, max_attempts)`.

    Wait, `attempts_made` logic:
    If `max_attempts` is 4.
    Attempt 1: fails.
    Attempt 2: fails.
    Attempt 3: fails.
    Attempt 4: fails.
    Return `(False, 4)`.
    If Attempt 1: succeeds.
    Return `(True, 1)`.

    The logic `attempts += 1` before the `try` block handles this correctly.

    One more check: `get_next_job` return type `tuple[str, dict] | None`.
    Python 3.10+ syntax `|` is allowed? The prompt says "Use only the standard library". Usually, this implies standard types. I will use `Optional` or `Union` or the `|` pipe depending on standard usage. Since I'm writing Python code, `|` is standard in 3.10+.

    One detail: "Do NOT actually sleep". I'll ensure no `time.sleep`.

    Let's double check `JobQueue.process_job`.
    It must use `RetryPolicy`.
    I'll instantiate `RetryPolicy` in `JobQueue.__init__`.

    Actually, I'll make sure `JobQueue` uses a single instance of `RetryPolicy`.

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

    @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[[dict]], data: dict) -> Tuple[bool, int]:
            attempts = 0
            while attempts < self.max_attempts:
                attempts += 1
                try:
                    fn(data)
                    return True, attempts
                except Exception:
                    pass
            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[[dict]]) -> 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) -> Union[Tuple[str, dict], None]:
            if not self._jobs:
                return None
            
            max_priority = max(j.priority for j in self._jobs)
            for j in self._jobs:
                if j.priority == max_priority:
                    return (j.id, j.data)
            return None
    ```

    Wait, the `process_job` signature: `process_job(self, job_id: str, Processor: Callable)`.
    My code follows that.

    Wait, `get_next_job` return type in prompt: `tuple[str, dict] | None`.
    I will use the `|` pipe as it's cleaner and matches the prompt's hint.

    Final check on `RetryPolicy.run` signature: `run(self, fn: Callable, data) -> tuple[bool, int]`.
    The prompt doesn't specify the type of `data` in the `run` signature, but `Job` has `data: dict`.

    One minor thing: `JobQueue.process_job`. If I find the job, I use the retry policy.

    One thought: Should `process_job` remove the job?
    In most queue implementations, `process` or `consume` removes the item.
    However, `get_next_job` also exists.
    If `get_next_job` doesn't remove it, and `process_job` doesn't remove it, then the queue is just a list that you can peek at or execute by ID.
    Given the instructions are very specific about signatures, I will stick to the signatures exactly.

    Actually, "FIFO order within the same priority".
    If I don't remove the job, and I call `get_next_job` multiple times, it will always return the same first job of the highest priority.
    If the intention was to "pop" it, the prompt would usually say "pop" or "remove".
    But if I don't remove it, is it a "Queue"?
    Maybe `process_job` should remove it?
    If `process_job` removes it, then the queue state changes.
    I'll stick to the signatures. No removals.

    Wait, one detail: `RetryPolicy.run(self, fn: Callable, data)`.
    The prompt uses `data` (no type). I'll just use `data`.

    Refining the `JobQueue.get_next_job` to be more efficient than O(N) find-max?
    With a list, `max()` is O(N). It's fine.

    Wait, I just noticed the prompt says:
    `process_job(self, job_id: str, Processor: Callable) -> bool`
    The word "must USE RetryPolicy (compose, don't reimplement retry logic)".
    My implementation does that.

    Final Code check:
    - `dataclass` used? Yes.
    - `RetryPolicy` has `run`? Yes.
    - `JobQueue` has `add_job`, `process_job`, `get_next_job`? Yes.
    - `get_next_job` handles FIFO/Priority? Yes.
    - No sleep? Yes.
    - Standard library only? Yes.
    - Python code only? Yes.
</reasoning_content>

<content>
```python
from dataclasses import dataclass
from typing import Callable, Tuple, Dict, List, Optional, Union

@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[[dict]], data: dict) -> 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: 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[[dict]]) -> 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) -> Union[Tuple[str, dict], None]:
        if not self._jobs:
            return None
        
        # Find highest priority
        max_priority = max(j.priority for j in self._jobs)
        
        # Return first occurrence (FIFO) of highest priority
        for j in self._jobs:
            if j.priority == max_priority:
                return (j.id, j.data)
        return None
```
</content>