<reasoning_content>
*   Goal: Refactor a job queue into three composable classes (`Job`, `RetryPolicy`, `JobQueue`) in `solution.py`.
    *   Constraint: Standard library only, no external packages.
    *    Constraint: No explanations, only code.

    *   `Job`:
        *   Dataclass.
        *   Fields: `id: str`, `data: dict`, `priority: int = 0`, `retries: int = 0`.
    *   `RetryPolicy`:
        *   `__init__(self, max_attempts: int = 4)`
        *   `run(self, fn: Callable, data) -> tuple[bool, int]`
        *   Logic: Call `fn(data)`, retry on exception until success or `max_attempts`.
        *   Constraint: No `sleep`.
        *   Return: `(success, attempts_made)`.
    *   `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`
        *   Ordering: Highest priority first, FIFO within same priority.
        *   Constraint: `process_job` must use `RetryPolicy`.

    *   Imports: `dataclasses.dataclass`, `typing.Callable`, `heapq` (for the priority queue).
    *   `Job`: Standard dataclass. Since `heapq` is min-heap, and I need highest priority first, I'll store priority as negative or handle comparison. Actually, the requirement says "highest-priority". If priority is 10 (high) and 0 (low), I should use negative values in a min-heap or just sort/manage a list. Using a list with `sort` or `heapq` is common. Let's use a list and sort or a list of lists for FIFO.
    *   *Correction on `JobQueue` ordering:* "Highest-priority job's (id, data); FIFO order within the same priority."
        *   To implement FIFO with `heapq`, we need an entry count.
        *   Alternatively, a dictionary of lists where keys are priorities.
    *   `RetryPolicy`: Simple loop with `try-except`.
    *   `JobQueue`:
        *   Store jobs. A dictionary `self.jobs = {}` for easy lookup by ID (needed for `process_job`) and a list/heap for ordering.
        *   Actually, `process_job` takes a `job_id`. I need to find the job with that ID.
        *   `get_next_job` needs to find the highest priority.

    *   *Priority Queue implementation:*
        To handle highest priority (max heap behavior) and FIFO:
        A heap of tuples: `(-priority, entry_count, job_id)`.
        The `entry_count` ensures FIFO for equal priorities.

    *   *Job storage:*
        Need a way to map `job_id` to `Job` objects because `process_job` takes `job_id`.

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

    *   `JobQueue` class:
        ```python
        class JobQueue:
            def __init__(self):
                self.jobs = {} # id -> Job
                self.heap = [] # list for heapq
                self.counter = 0 # for FIFO
                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 is min-heap. To get highest priority first, use -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, _ = self.retry_policy.run(processor, job.data)
                return success

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

    *   `Job`: dataclass? Yes. Fields correct? Yes.
    *   `RetryPolicy`: `max_attempts`? Yes. `run` signature? Yes. No sleep? Yes.
    *   `JobQueue`: `add_job`? Yes. `process_job`? Yes. `get_next_job`? Yes.
    *   Ordering: Highest priority? Yes (using `-priority`). FIFO? Yes (using `counter`).
    *   Composition: `process_job` uses `RetryPolicy`? Yes.

    Refining `JobQueue`:
    The prompt asks for `JobQueue` to orchestrate Jobs using `RetryPolicy`.
    I should probably instantiate `RetryPolicy` inside `JobQueue` or pass it in. The prompt says "orchestrates Jobs using RetryPolicy", usually implying it owns one or is composed with one.

    Wait, let's look at `get_next_job`. It returns `(id, data)`.
    If I use `heapq`, I need to make sure I don't have issues if the job is removed/processed.
    If `process_job` is called, should it be removed from the queue? The prompt doesn't specify removal on `process_job`, only on `get_next_job`. However, usually, `get_next_job` removes it from the queue.

    Wait, `process_job` takes a `job_id`. If `get_next_job` removes it, `process_job` won't find it if called after. But usually, these are two different ways to access.
    Let's assume `get_next_job` pops from heap.

    Actually, looking at the signature:
    `process_job(self, job_id: str, processor: Callable) -> bool`
    `get_next_job(self) -> tuple[str, dict] | None`

    If `get_next_job` pops, the job is gone from the priority ordering. This is standard behavior for a queue.

    One detail: `RetryPolicy` return signature `tuple[bool, int]`.
    `attempts_made` logic:
    If `max_attempts` is 4:
    Attempt 1 (fails) -> `attempts_made` = 1
    Attempt 2 (fails) -> `attempts_made` = 2
    Attempt 3 (fails) -> `attempts_made` = 3
    Attempt 4 (fails) -> `attempts_made` = 4
    Return `(False, 4)`.
    If Attempt 1 (success) -> Return `(True, 1)`.

    My code:
    ```python
    attempts_made = 0
    while attempts_made < self.max_attempts:
        try:
            fn(data)
            return True, attempts_made + 1
        except Exception:
            attempts_made += 1
    return False, attempts_made
    ```
    This works.

    Wait, the `Job` dataclass has a `retries` field. Should `process_job` update that? The prompt doesn't explicitly say to update the `retries` field in the `Job` object, but usually that's why it's there. However, `RetryPolicy` doesn't know about the `Job` object, it only knows about the `fn` and `data`.
    Since `RetryPolicy` is meant to be "composable", and `process_job` is meant to "use" it, I'll keep it simple. The `retries` field in `Job` might be for external tracking or if `RetryPolicy` was designed to mutate the `Job` object (but the signature `run(self, fn: Callable, data)` doesn't pass the `Job` object).

    Let's double check `get_next_job`: "return the highest-priority job's (id, data); FIFO order within the same priority."
    Standard Heapq logic:
    `heapq.heappush(self.heap, (-priority, self.counter, job_id))`
    Smallest value is popped first.
    Smallest `-priority` means largest `priority`.
    Smallest `counter` means earliest entry.
    This perfectly satisfies the requirement.

    Final check on imports:
    `from dataclasses import dataclass`
    `from typing import Callable`
    `import heapq`

    One small detail: `process_job` must use `RetryPolicy`.
    If `process_job` is called, and the job was already popped by `get_next_job`, should it still work?
    If `get_next_job` pops, the job is removed from the heap but is it removed from `self.jobs`?
    Usually, "Get next job" removes it from the queue.
    If `process_job` is called with a `job_id`, it should still work even if it's no longer "in" the queue (i.e., not in the heap), as long as it exists in the system.
    To be safe, I will keep the `jobs` dictionary as the source of truth for existence.

    Wait, the prompt says `JobQueue` orchestrates `Jobs` using `RetryPolicy`.
    Should I allow `RetryPolicy` to be passed into `JobQueue`?
    "orchestrates Jobs using RetryPolicy" usually implies `JobQueue` has a `RetryPolicy` instance.

    ```python
    from dataclasses import dataclass
    from typing import Callable
    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: dict) -> tuple[bool, int]:
            attempts_made = 0
            while attempts_made < self.max_attempts:
                try:
                    fn(data)
                    return True, attempts_made + 1
                except Exception:
                    attempts_made += 1
            return False, attempts_made

    class JobQueue:
        def __init__(self):
            self.jobs = {}
            self.heap = []
            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, _ = self.retry_policy.run(processor, job.data)
            return success

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

    One minor thing: the `Job` dataclass has `retries: int = 0`.
    Since `RetryPolicy` doesn't take the `Job` object, the `retries` count won't be updated unless I modify `process_job` to update it. But the prompt doesn't ask to update it. It just asks to define the dataclass with that field.

    Double check: `process_job` -> `must USE RetryPolicy (compose, don't reimplement retry logic)`.
    My implementation: `success, _ = self.retry_policy.run(processor, job.data)`.
    This is composition.

    Wait, `JobQueue.get_next_job` return type: `tuple[str, dict] | None`.
    Python 3.10+ syntax for union types is fine. Since I don't know the python version but standard practice for these prompts is modern Python.

    One more look: `RetryPolicy.run` signature: `run(self, fn: Callable, data)`.
    My code: `def run(self, fn: Callable, data: dict) -> tuple[bool, int]:`. Correct.

    Wait, I should make sure `RetryPolicy` handles the `data` correctly. `fn(data)` is called.

    Wait, I notice a tiny detail: `process_job` signature: `process_job(self, job_id: str, processor: Callable) -> bool`.
    My code: `def process_job(self, job_id: str, processor: Callable) -> bool:`. Correct.

    Final consideration: `heapq` and `dataclass` are standard library.

    Final Code Structure:
    ```python
    from dataclasses import dataclass
    from typing import Callable
    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: dict) -> tuple[bool, int]:
            attempts_made = 0
            while attempts_made < self.max_attempts:
                try:
                    fn(data)
                    return True, attempts_made + 1
                except Exception:
                    attempts_made += 1
            return False, attempts_made

    class JobQueue:
        def __init__(self):
            self.jobs = {}
            self.heap = []
            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, _ = self.retry_policy.run(processor, job.data)
            return success

        def get_next_job(self) -> tuple[str, dict] | None:
            if not self.heap:
                return None
            _, _, job_id = heapq.heappop(self.heap)
            job = self.jobs.get(job_id)
            if not job:
                return None
            return job.id, job.data
    ```
    Actually, `JobQueue` doesn't need to handle the case where a job is popped from the heap but doesn't exist in the dict (unless jobs are deleted, which isn't part of the spec). I'll keep it simple.

    Wait, `JobQueue.get_next_job` returns `(id, data)`.
    One last check: `tuple[bool, int]` return type for `RetryPolicy`. Yes.

    One logic check: `RetryPolicy.run` retries on "any exception". `except Exception:` is correct.

    One detail on `RetryPolicy.run`: `attempts_made` should start at 0.
    If `max_attempts` is 4.
    Loop 0: `attempts_made` (0) < 4. `fn(data)` fails. `attempts_made` becomes 1.
    Loop 1: `attempts_made` (1) < 4. `fn(data)` fails. `attempts_made` becomes 2.
    Loop 2: `attempts_made` (2) < 4. `fn(data)` fails. `attempts_made` becomes 3.
    Loop 3: `attempts_made` (3) < 4. `fn(data)` fails. `attempts_made` becomes 4.
    Loop 4: `attempts_made` (4) < 4 is False.
    Returns `(False, 4)`. Correct.

    If success on first try:
    Loop 0: `attempts_made` (0) < 4. `fn(data)` succeeds.
    Returns `(True, 1)`. Correct.

    Wait, `JobQueue.process_job` returns `bool`.
    `success, _ = self.retry_policy.run(processor, job.data)`
    `return success`
    Correct.

    One more thing: "WFIO order within same priority".
    Heap entry: `(-priority, self._counter, job_id)`
    Since `-priority` is the same, it looks at `self._counter`.
    Smaller `_counter` (earlier added) will be popped first.
    Correct.

    Wait, the instructions say "Do NOT actually sleep".
    My code does not have `time.sleep`.

    Wait, one tiny thing. I used `self._counter`. The prompt says `counter` is not a field, but I can name it whatever I want internally. I'll use `self._counter`. Or just `self.counter`.
</reasoning_content>

<content>
```python
from dataclasses import dataclass
from typing import Callable
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: dict) -> tuple[bool, int]:
        attempts_made = 0
        while attempts_made < self.max_attempts:
            try:
                fn(data)
                return True, attempts_made + 1
            except Exception:
                attempts_made += 1
        return False, attempts_made

class JobQueue:
    def __init__(self):
        self.jobs = {}
        self.heap = []
        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 negative priority for max-heap behavior with heapq min-heap
        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, _ = self.retry_policy.run(processor, job.data)
        return success

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