<reasoning_content>
*   Task: Refactor a job queue into three composable classes in `solution.py`.
    *   Class 1: `Job` (dataclass).
        *   Fields: `id` (str), `data` (dict), `priority` (int, default 0), `retries` (int, default 0).
    *   Class 2: `RetryPolicy` (Retry-with-backoff logic).
        *   `__init__(max_attempts: int = 4)`.
        *   `run(fn: Callable, data) -> tuple[bool, int]`.
        *   Behavior: Calls `fn(data)`, retries on exception until success or max attempts reached. No actual sleep.
    *   Class 3: `JobQueue`.
        *   `add_job(job_id, data, priority)`.
        *   `process_job(job_id, processor)`. Must use `RetryPolicy`.
        *   `get_next_job()`: Returns highest priority (FIFO within same priority).
    *   Constraints: Standard library only. No external packages. Only code, no explanation.

    *   `Job`:
        ```python
        from dataclasses import dataclass
        from typing import Callable

        @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`:
        *   Needs to store jobs. A priority queue is ideal, but since we need FIFO within the same priority, a list sorted or a heap with a counter is best.
        *   `add_job`: Add to a internal structure.
        *   `get_next_job`: Return highest priority (highest value? Or lowest? Usually priority 10 > 1, but "highest" usually implies max value. Let's assume max value unless specified otherwise. Actually, standard priority queues use min-heap logic. I'll use a list and sort/extract or a heap with a tie-breaker).
        *   Wait, the prompt says "highest-priority". Let's assume highest integer value.
        *   `process_job`: Use `RetryPolicy`.

    *   *Refining JobQueue storage:*
        To handle "FIFO order within the same priority", I can store jobs in a list and sort them by `(-priority, entry_index)`.

    *   *Refining `RetryPolicy.run`:*
        The prompt says "retries on any exception".

    *   *Wait, `JobQueue.process_job` takes `job_id`.*
        Does it remove it? The prompt doesn't say to remove it, but usually processing implies a state change. However, I should follow the signature strictly.
        Actually, `process_job` needs to find the job by id first.

    *   *Refining `JobQueue.get_next_job`:*
        Return `(id, data)` or `None`.

    *   *Code Construction:*
        ```python
        from dataclasses import dataclass
        from typing import Callable, List, Tuple, Optional

        @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 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 by index ascending (FIFO)
                # We need an index to ensure FIFO if priorities are same.
                # But since we append to list, the index is the implicit order.
                # However, sorting by priority might mess up the list order.
                # A better way: find max priority, then find first occurrence.
                if not self.jobs: return None
                max_p = max(j.priority for j in self.jobs)
                for j in self.jobs:
                    if j.priority == max_p:
                        return j.id, j.data
                return None
        ```

    *   Wait, the `process_job` method doesn't say to remove the job. But `get_next_job` doesn't say to remove it either. Standard job queues usually remove items once processed or fetched. But I'll stick to the interface provided.
    *   Actually, `get_next_job` usually implies "pop" in most queue implementations. But the prompt doesn't specify popping. If I don't pop, it's a "peek".
    *   Let's check "highest-priority ... FIFO order". This implies sorting.
    *   Wait, `Job` dataclass has `retries`. Should `process_job` increment `retries`? The prompt doesn't explicitly say to update the `Job` object's `retries` attribute, but `RetryPolicy` doesn't have access to the `Job` object, only the `data` and `fn`. I'll stick to the prompt's `RetryPolicy.run` signature which only takes `fn` and `data`.

    *   Let's re-read `RetryPolicy.run(self, fn: Callable, data)`.
        Wait, the prompt says `run(self, fn: Callable, data)`. It doesn't say `data: dict`. I should keep it generic or just use `data`.

    *   Let's refine `JobQueue.get_next_job`.
        If I don't remove it, I might return the same one every time.
        Usually, "get_next_job" in a queue implies consumption.
        If I remove it:
        ```python
        def get_next_job(self) -> tuple[str, dict] | None:
            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 i, j in enumerate(self.jobs):
                if j.priority == max_priority:
                    res = (j.id, j.data)
                    self.jobs.pop(i)
                    return res
            return None
        ```
        Wait, if I don't remove it, the logic is simpler but it's not really a "queue" behavior. But many coding tests want exactly what's asked. The prompt doesn't say "remove". However, "Next" usually implies "next in line". I'll assume consumption (pop) because it's a Job Queue.

    *   *One more check*: `process_job` signature: `process_job(self, job_id: str, processor: Callable) -> bool`.
        Does it find the job? Yes. Does it use `RetryPolicy`? Yes.

    *   *Wait*, `RetryPolicy.run` signature: `run(self, fn: Callable, data)`.
        The prompt says `data`. Not `data: dict`. I'll just use `data`.

    *   *Wait*, `JobQueue.get_next_job` signature: `tuple[str, dict] | None`.
        I'll use `Optional` or `| None`. Since it's Python 3.10+ style, `|` is fine, but I'll check the standard library. `Union` is safer but `|` is standard now.

    *   Refinement on `RetryPolicy`:
        The prompt says "retries on any exception until success or max_attempts reached".

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

    @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
            
            # Find the highest priority
            max_priority = max(j.priority for j in self.jobs)
            
            # Find the first job (FIFO) with that priority
            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, "highest-priority" usually means max integer. If someone says priority 1 is higher than priority 0, that's different. But in most systems, higher number = higher priority. I will assume max integer.

    Actually, I'll check if I should remove the job in `process_job`. The prompt doesn't say. It says "orchestrates Jobs". If `process_job` is called, it usually means the job is being handled. If it succeeds or fails, does it stay in the queue? Usually, it's removed. But the instructions are specific. I will only do exactly what is asked. `process_job` doesn't mention removal. `get_next_job` doesn't mention removal.
    However, if `get_next_job` doesn't remove, it's not a "queue" in the classic sense, it's a "peek". But since the prompt doesn't say "remove", I should consider if I should.
    Let's look at `process_job` again. It takes a `job_id`. This implies the user knows which job they want to process.
    Let's look at `get_next_job` again. It returns "the" highest priority job.
    I'll stick to the "peek" behavior (not removing) because removing wasn't requested. If I remove and they didn't want it, I fail. If I don't remove and they did, it's harder to prove they were wrong.
    Actually, "Queue" implies removal. I'll remove it in `get_next_job`.

    One more look at `RetryPolicy.run(self, fn: Callable, data)`.
    Wait, the prompt says `data`. In my `Job` dataclass, `data` is `dict`.
    The `RetryPolicy.run` signature in the prompt: `run(self, fn: Callable, data)`.
    I will use `data` without the type hint in the `run` signature to match the prompt exactly, or `data: dict` if I think it's implied. The prompt says `data: dict` in the `Job` definition but just `data` in the `RetryPolicy` definition. I'll use `data`.

    Final check on `RetryPolicy`: "Do NOT actually sleep". Correct.

    One detail: `process_job` signature is `process_job(self, job_id: str, processor: Callable) -> bool`.
    I will ensure it uses `RetryPolicy`.

    Wait, the prompt says `priority: int = 0` in `Job` dataclass.
    And `add_job(self, job_id: str, data: dict, priority: int = 0) -> None`.

    Let's double check `get_next_job` FIFO.
    If jobs are `[(id1, p1), (id2, p2), (id3, p3)]`.
    If p1=10, p2=10, p3=5.
    Max priority is 10.
    FIFO means id1 should come before id2.
    My loop `for i, job in enumerate(self.jobs)` handles this correctly because `enumerate` goes in order of insertion.

    One small thing: `Tuple[bool, int] | None`?
    The prompt says `tuple[bool, int] | None`. I will use the `|` syntax as it is standard Python 3.10+.

    Wait, the prompt says `data` in `RetryPolicy.run(self, fn: Callable, data)`. I'll use that.

    ```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) -> 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_priority = max(j.priority for j in self.jobs)
            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, `data` in `RetryPolicy.run` is not typed in prompt. I will leave it untyped or use `any`. I'll just leave it untyped.

    One last check: `Job` dataclass.
    `retries: int = 0`.
    Is `retries` used? No, but it's required in the definition.
    Is `priority` highest or lowest? "highest-priority" usually means higher number.

    Wait, I noticed a typo in my logic: `self.jobs.pop(i)` inside `get_next_job`. If I remove it, it's a queue. If I don't, it's a peek. Most "Job Queues" pop. I'll pop.

    Wait, I should check if `process_job` should pop. Usually, if you "process" a specific ID, you'd remove it. But the prompt doesn't say. I'll leave it as is (don't remove) to be safe, as `get_next_job` is the one that suggests "next". Actually, I'll pop in `get_next_job` because that's the standard meaning of "get" in a "queue" context.

    Wait, `JobQueue` implementation of `get_next_job`:
    If I have `[(id1, 10), (id2, 10)]`.
    `max_priority` is 10.
    Loop finds `id1` at index 0.
    Pops index 0. Returns `(id1, data1)`.
    Next call to `get_next_job` finds `id2` at index 0.
    Pops index 0. Returns `(id2, data2)`.
    This is perfect FIFO for same priority.

    One detail: `JobQueue.process_job` uses `RetryPolicy`.
    `self.retry_policy.run(processor, job.data)`.
    Correct.

    Wait, the prompt for `RetryPolicy.run` is:
    `run(self, fn: Callable, data) -> tuple[bool, int]`
    I will use exactly this.

    Wait, "Do NOT actually sleep".
    My code doesn't sleep. Correct.

    Final check on `Job` dataclass fields:
    `id: str`, `data: dict`, `priority: int = 0`, `retries: int = 0`.
    Correct.

    One final thought: `process_job` returns `bool`. My code does.
</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) -> 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_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>