<reasoning_content>
*   Target: Refactor a job queue into three composable classes in `solution.py`.
    *   Requirement 1: `Job` dataclass (`id`, `data`, `priority`, `retries`).
    *   Requirement 2: `RetryPolicy` class (encapsulates retries with backoff logic, but *no actual sleeping*). `run` method returns `(success, attempts_made)`.
    *   Requirement 3: `JobQueue` class (orchestrates `Job`s using `RetryPolicy`). Methods: `add_job`, `process_job`, `get_next_job`.
    *   Ordering for `get_next_job`: Highest priority first, FIFO within same priority.
    *   Constraints: Standard library only, no external packages, only code (no explanations).

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

        @dataclass
        class Job:
            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: Try `fn(data)`. If exception, retry until `max_attempts`.
        *   Note: "Do NOT actually sleep".

        ```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`:
        *   `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`
        *   Order: Highest priority, FIFO within same priority.

        *Implementation details for `JobQueue`:*
        - Storage: A list of `Job` objects or a priority queue?
        - Since it's FIFO within same priority, a priority queue is slightly tricky with `(priority, timestamp)`. A sorted list or manual sorting is fine given the prompt doesn't specify scale.
        - Let's use a list and sort it every time or keep it sorted.
        - `priority` logic: Higher number = higher priority? Usually, priority queues are min-heaps, so "highest priority" usually means the largest number or the smallest? Standard convention: Higher number = Higher priority. Let's assume standard "highest number = highest priority".
        - Wait, `get_next_job` returns highest priority. If I use a list, I can sort by `-priority` then by insertion order.

        Wait, let's rethink `get_next_job`.
        `priority`: let's say `priority=10` is higher than `priority=1`.
        FIFO within same priority:
        If we have jobs A (p=10) added at time 1, and B (p=10) added at time 2. A should come first.
        If we have jobs C (p=5) added at time 3. C comes after A and B.

        Data structure: `self.jobs: list[Job]`.
        `add_job`: append to list.
        `get_next_job`: Sort list by `priority` descending, then keep original order (stable sort). Then pop the first.

        `process_job`: find job by ID, use `RetryPolicy`.

        *Revised `JobQueue` structure:*
        ```python
        class JobQueue:
            def __init__(self):
                self.jobs = []
                self.retry_policy = RetryPolicy()

            def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
                self.jobs.append(Job(job_id, data, 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. Python's sort is stable.
                self.jobs.sort(key=lambda x: x.priority, reverse=True)
                job = self.jobs.pop(0)
                return job.id, job.data
        ```

    - `Job` dataclass? Yes.
    - `RetryPolicy` with `max_attempts` and `run`? Yes.
    - `RetryPolicy` no sleep? Yes.
    - `JobQueue` composition? Yes.
    - `JobQueue` FIFO within same priority? Yes (using stable sort on a list).
    - Only standard library? Yes.
    - Only Python code? Yes.

    Wait, I should probably use a counter for insertion order just to be safe for the FIFO part if I were using a heap, but since I'm using `list.sort()` which is stable, the order is preserved.

    Let's double check `get_next_job`: "highest-priority job's (id, data); FIFO order within the same priority."
    If I use `self.jobs.sort(key=lambda x: x.priority, reverse=True)`, the elements with the same priority maintain their relative original order. `pop(0)` takes the first one. This satisfies the requirement perfectly.

    Actually, to make it more efficient, I can sort inside `get_next_job` or just ensure the list is always sorted. But for a simple refactor task, sorting in the getter is clean.

    One detail: `Job` has `retries` field. `RetryPolicy` doesn't modify the `Job` object's `retries` field in the prompt's signature, but `Job` has it. The prompt doesn't specify updating `retries` in the `Job` object, only returning `attempts_made`. I will stick strictly to the signatures provided.

    Wait, `process_job` signature: `process_job(self, job_id: str, processor: Callable) -> bool`.
    I need to find the job by ID.

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

        def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
            self.jobs.append(Job(job_id, data, 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
            # Sort by priority descending (highest first). 
            # Python's sort is stable, preserving FIFO order for same priority.
            self.jobs.sort(key=lambda x: x.priority, reverse=True)
            job = self.jobs.pop(0)
            return job.id, job.data
    ```

    Actually, `retry_policy` should be an instance variable to "compose". Or maybe it's passed? The prompt says "orchestrates Jobs using RetryPolicy... must USE RetryPolicy (compose, don't reimplement)". This implies an instance of `RetryPolicy` inside `JobQueue`.

    One small check: `get_next_job` signature returns `tuple[str, dict] | None`.
    Python 3.10+ `|` syntax is fine. If older, `Optional` or `Union`. I'll use `|` as it's standard in modern Python. If not, I'll stick to standard imports. Wait, the prompt says "Use only the standard library". `typing.Union` is standard library. But `|` is standard syntax.

    Wait, `Job` dataclass fields: `id: str`, `data: dict`, `priority: int = 0`, `retries: int = 0`.
    `RetryPolicy` `run` signature: `run(self, fn: Callable, data) -> tuple[bool, int]`. Note: missing type for `data` in prompt description, but it's implied as `dict` from `Job`. I'll type it for safety.

    Let's refine `process_job` to ensure I don't modify the list while iterating if I were doing something complex, but `next(...)` is safe.

    One more look at `RetryPolicy`: "calls `fn(data)`, retries on any exception".
    Should I catch `Exception` or `BaseException`? Usually `Exception` is safer.

    Final check on `JobQueue.get_next_job`:
    "return the highest-priority job's (id, data); FIFO order within the same priority."
    Example:
    1. Add(id='A', priority=1)
    2. Add(id='B', priority=2)
    3. Add(id='C', priority=2)
    `get_next_job()`:
    Sorted list: [B (p=2, index 1), C (p=2, index 2), A (p=1, index 0)]
    Pop(0) -> B.
    Correct.

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

    Wait, `RetryPolicy` signature: `run(self, fn: Callable, data) -> tuple[bool, int]`.
    The `data` argument in `run` doesn't have a type hint in the prompt's description text but `Job` does. I'll leave the type hint off or put `dict` if I want to be safe. The prompt says `data: dict` in `Job` and `data` in `run`.

    ```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_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: 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
            # Stable sort ensures FIFO for same priority.
            self.jobs.sort(key=lambda x: x.priority, reverse=True)
            job = self.jobs.pop(0)
            return job.id, job.data
    ```

    Wait, `JobQueue.process_job` - if I process a job, should it be removed?
    The prompt doesn't specify removing it. Usually `process_job` is an action, and `get_next_job` is a retrieval.
    However, `get_next_job` pops it.
    If `process_job` is called, it doesn't say to remove. But if `get_next_job` is used, it is removed.
    The prompt signature for `process_job` is `process_job(self, job_id: str, processor: Callable) -> bool`.
    If the user calls `get_next_job` then `process_job`, it would fail because `get_next_job` pops it.
    Usually, one would either `get` and then `process`, or `process` directly.
    Given the provided signatures, I will just implement them exactly as requested.
</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_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: 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
        
        # Python's sort is stable, preserving FIFO order for same priorities.
        self.jobs.sort(key=lambda x: x.priority, reverse=True)
        job = self.jobs.pop(0)
        return job.id, job.data
```
</content>