<reasoning_content>
*   Goal: Refactor a job queue into three composable classes in `solution.py`.
    *   Constraint: Use only the standard library. No explanations.

        *   Attributes: `id: str`, `data: dict`, `priority: int = 0`, `retries: int = 0`.
        *   Decorator: `@dataclass`.

        *   Initialization: `max_attempts: int = 4`.
        *   Method: `run(self, fn: Callable, data) -> tuple[bool, int]`.
        *   Logic: Call `fn(data)`, catch exceptions, retry up to `max_attempts`. Do *not* sleep.
        *   Returns: `(success, attempts_made)`.

        *   Method `add_job(self, job_id: str, data: dict, priority: int = 0) -> None`.
        *   Method `process_job(self, job_id: str, processor: Callable) -> bool`.
        *   Method `get_next_job(self) -> tuple[str, dict] | None`.
        *   Logic: `process_job` must use `RetryPolicy`. `get_next_job` must return highest-priority job, FIFO for ties.

    *   *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:*
        *   Storage: A list of `Job` objects.
        *   Ordering: Sort by `priority` (desc) and then insertion order (FIFO). Since `JobQueue` is a stateful object, and `get_next_job` returns the next job, I should probably remove it from the list or keep track of a cursor. Actually, `get_next_job` usually implies "pop" or "peek and remove". Let's assume "pop" or find and remove to maintain the queue state correctly.
        *   Sorting: `priority` is usually higher = better, but I should check if it's max-priority. The prompt says "highest-priority". Usually, higher numbers = higher priority.
        *   Tie-breaking: FIFO.
        *   Storage implementation: A list where we append new jobs. When getting the next job, we find the best one, remove it, and return it.
        *   Wait, "FIFO order within the same priority" suggests I should sort by `-priority` then by the original index.

        ```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(id=job_id, data=data, priority=priority))

            def get_next_job(self) -> tuple[str, dict] | None:
                if not self.jobs:
                    return None
                # Sort by priority (descending), then by insertion order (implicit by list order)
                # To do this properly while maintaining FIFO:
                # We find the max priority first.
                max_p = max(j.priority for j in self.jobs)
                for i, job in enumerate(self.jobs):
                    if job.priority == max_p:
                        res = (job.id, job.data)
                        self.jobs.pop(i)
                        return res
                return None

            def process_job(self, job_id: str, processor: Callable) -> bool:
                # Find the job
                job_obj = None
                for j in self.jobs:
                    if j.id == job_id:
                        job_obj = j
                        break

                if not job_obj:
                    return False

                success, _ = self.retry_policy.run(processor, job_obj.data)
                return success
        ```

    *   *Wait, the order logic in `get_next_job`:*
        "return the highest-priority job's (id, data); FIFO order within the same priority."
        If I have:
        Job A: priority 10, added first
        Job B: priority 10, added second
        Job C: priority 20, added third
        `get_next_job` should return C.
        Then, if I have:
        Job A: priority 10, added first
        Job B: priority 10, added second
        `get_next_job` should return A.

        Implementation check:
        1. Find max priority.
        2. Return the first job in the list that has that max priority.
        3. Remove it.

    *   *Correction on `process_job`:*
        The prompt says "must USE RetryPolicy (compose, don't reimplement retry logic)".
        I have a `RetryPolicy` instance in `JobQueue`.

    *   *Wait, `RetryPolicy.run` signature:*
        `run(self, fn: Callable, data) -> tuple[bool, int]`
        Does `data` need to be explicitly typed? The prompt says `data`. Python 3.10+ syntax for `tuple[bool, int]` is allowed.

    *   *Refining `JobQueue.process_job`*:
        Should I remove the job from the queue if it's processed? The prompt doesn't explicitly say to remove it during `process_job`, but usually, a process method consumes it. However, `get_next_job` is the one that returns it. If I call `process_job` with a specific ID, it just executes it. I'll stick to the instructions: "orchestrates Jobs using RetryPolicy".

    *   *Refining `JobQueue.get_next_job`*:
        If I use `pop`, I must ensure the index logic is correct.

    *   *Refining imports*:
        `from dataclasses import dataclass`
    *   *Refining type hints*:
        `from typing import Callable`

    *   *One detail*: `JobQueue` needs to hold `RetryPolicy`.

    ```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 get_next_job(self) -> tuple[str, dict] | None:
            if not self.jobs:
                return None
            
            # Find highest priority
            max_priority = max(job.priority for job in self.jobs)
            
            # Find first job with that priority (FIFO)
            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

        def process_job(self, job_id: str, processor: Callable) -> bool:
            target_job = None
            for job in self.jobs:
                if job.id == job_id:
                    target_job = job
                    break
            
            if target_job is None:
                return False
            
            success, _ = self.retry_policy.run(processor, target_job.data)
            return success
    ```

    Wait, one small detail. The prompt says `retry: int = 0` in `Job`. Does `RetryPolicy` update `retries`?
    The prompt says `RetryPolicy` returns `(success, attempts_made)`. It doesn't explicitly say it must update the `Job` object's `retries` field. Since `JobQueue.process_job` returns a bool and `RetryPolicy` returns a tuple, and `process_job` must use `RetryPolicy`, I will just use the return value of `run`. I'll leave `retries` on the dataclass as requested but don't see a specific instruction to update it within `process_job`.

    Let's double check `get_next_job`.
    "return the highest-priority job's (id, data); FIFO order within the same priority."
    If I have:
    1. ID:1, Priority:10
    2. ID:2, Priority:10
    3. ID:3, Priority:20
    4. ID:4, Priority:20
    
    Execution order:
    1. `get_next_job` -> ID:3 (Highest priority 20, first seen).
    2. `get_next_job` -> ID:4 (Highest priority 20, next seen).
    3. `get_next_job` -> ID:1 (Highest priority 10, first seen).
    4. `get_next_job` -> ID:2 (Highest priority 10, next seen).
    
    My current logic:
    Find max priority (20). Find first index with 20 (ID:3). Pop.
    Next call: Find max priority (20). Find first index with 20 (ID:4). Pop.
    Next call: Find max priority (10). Find first index with 10 (ID:1). Pop.
    Next call: Find max priority (10). Find first index with 10 (ID:2). Pop.
    
    This satisfies the requirement perfectly.

    *Self-Correction on imports*:
    The prompt asks for `tuple[bool, int]` and `Callable`.
    In Python 3.9+, `tuple` is subscriptable. In older versions, `Tuple` from `typing` is needed.
    However, the prompt specifies `tuple[bool, int]`. This suggests Python 3.9+ syntax.
    I will include `from typing import Callable`.

    Final check on `Job` fields: `id`, `data`, `priority`, `retries`.
    Final check on `RetryPolicy` signature: `__init__(self, max_attempts: int = 4)`, `run(self, fn: Callable, data) -> tuple[bool, int]`.

    Wait, the `JobQueue.process_job` signature: `process_job(self, job_id: str, processor: Callable) -> bool`.
    I should make sure I find the job by `job_id`.

    One minor detail: `JobQueue` class definition. Should it contain the `RetryPolicy`? The prompt says "orchestrates Jobs using RetryPolicy". It's cleanest to instantiate it in `__init__`.

    One detail: `JobQueue.process_job` doesn't specify if it should remove the job. Usually, processing a job removes it, but since `get_next_job` is also a method, they might be used sequentially (get then process). If I call `process_job` directly, and then call `get_next_job`, the job would still be there unless I remove it in `process_job`. 
    The prompt doesn't say "remove". I'll leave it in the list to be safe, as `get_next_job` is the one that clearly implies taking a job out of the queue. Actually, standard queue behavior is that processing consumes. But I'll stick strictly to the provided signatures.

    Wait, "orchestrates Jobs using RetryPolicy".
    I'll make sure `RetryPolicy` is an instance variable of `JobQueue`.

    Final Code check:
    - `dataclass` used? Yes.
    - `max_attempts` used? Yes.
    - `run` returns `(success, attempts_made)`? Yes.
    - `do NOT actually sleep`? Yes.
    - `get_next_job` returns `tuple[str, dict] | None`? Yes.
    - `process_job` uses `RetryPolicy`? Yes.
    - Only standard library? Yes.

    One tiny thing: `JobQueue.process_job` logic. 
    If `process_job` is called with a `job_id` that doesn't exist, it should return `False`.

    ```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:
            target_job = None
            for job in self.jobs:
                if job.id == job_id:
                    target_job = job
                    break
            
            if target_job is None:
                return False
            
            success, _ = self.retry_policy.run(processor, target_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, `tuple[...] | None` syntax requires Python 3.10. The prompt used it in the description (`tuple[str, dict] | None`). I'll use that.

    Wait, looking at `RetryPolicy.run(self, fn: Callable, data)`.
    The prompt doesn't specify the type of `data` in the signature but `Job` says `data: dict`. I'll use `data: dict`.

    One check: `JobQueue` `process_job` finds the job, then calls `run`. If `run` succeeds, `process_job` returns `True`. If it fails after all attempts, `process_job` returns `False`. This is correct.

    Wait, "Write only the Python code, no explanations." - I will omit all markdown blocks except the code block.

    Final check on `Job` priority: 
    If priority is `10` and `5`, "highest-priority" is `10`. 
    My code: `max(j.priority for j in self.jobs)` correctly identifies `10`.

    Final check on FIFO:
    If `jobs` = `[Job(id='a', priority=10), Job(id='b', priority=10)]`.
    `max_priority` = 10.
    Loop finds index 0. Returns `('a', ...)`. Pops index 0.
    Next call: `max_priority` = 10.
    Loop finds index 0 (which is now 'b'). Returns `('b', ...)`. Pops index 0.
    Correct.

    Actually, `data` in `RetryPolicy.run` signature doesn't have a type hint in the prompt's text: `run(self, fn: Callable, data) -> tuple[bool, int]`. I'll leave it untyped or use `any`? I'll leave it untyped to match the prompt exactly. No, the prompt has `data` as a parameter. I'll just use `data`.

    Double checking `Job` attributes:
    `id: str`, `data: dict`, `priority: int = 0`, `retries: int = 0`.
    Done.
</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:
        target_job = None
        for job in self.jobs:
            if job.id == job_id:
                target_job = job
                break
        
        if target_job is None:
            return False
        
        success, _ = self.retry_policy.run(processor, target_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
```
</content>