<reasoning_content>
*   Goal: Refactor a job queue into three composable classes in `solution.py`.
    *   Class 1: `Job` (dataclass).
        *   Fields: `id: str`, `data: dict`, `priority: int = 0`, `retries: int = 0`.
    *   Class 2: `RetryPolicy` (logic encapsulation).
        *   `__init__(self, max_attempts: int = 4)`.
        *   `run(self, fn: Callable, data) -> tuple[bool, int]`.
        *   Retries `fn(data)` on exception until success or `max_attempts` reached.
        *   Do NOT sleep.
        *   Returns `(success, attempts_made)`.
    *   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`.
        *   Ordering: Highest priority first, FIFO within same priority.
        *   Composition: `process_job` must use `RetryPolicy`.
    *   Constraints: Standard library only, no external packages, Python code only (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`:
        ```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`:
        *   Need a data structure to store jobs. A list or a priority queue?
        *   Since priority is integer and FIFO within priority, a `heapq` or a sorted list works.
        *   Actually, simple list and sort/select is fine for a basic refactor, but let's consider efficiency.
        *   Wait, `get_next_job` needs the highest priority first.
        *   Let's use a list and sort it or just find the max.
        *   Actually, the standard way to handle priority + FIFO is to use a heap with `(-priority, insertion_index, job_id)`.
        *   However, let's keep it simple. A list of jobs.

    *   `JobQueue` methods:
        *   `add_job`: add to internal list.
        *   `process_job`: Find job by ID. Use `RetryPolicy` to run `Processor`. Update `retries` or just return success? The prompt doesn't specify updating `retries` in the object, but `RetryPolicy` tracks attempts.
        *   `get_next_job`: Find job with highest priority. If tie, oldest.

    *   *Refining `JobQueue` storage:*
        To handle "Highest priority first, FIFO within same priority", a list sorted by `(-priority, timestamp)` is ideal. Or just keep a list and sort/search.
        Let's use a list.

    *   *Refining `process_job`:*
        Does it update the job's retry count? The `Job` dataclass has `retries`. `RetryPolicy` returns `attempts_made`. It makes sense to update the `Job` object's `retries` property if it fails? Or maybe `process_job` just returns success.
        Actually, the prompt says `process_job` must *use* `RetryPolicy`.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Dict, Tuple, List, 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, attempts = self.retry_policy.run(Processor, job.data)
            job.retries = attempts
            return success

        def get_next_job(self) -> tuple[str, dict] | None:
            if not self.jobs:
                return None
            
            # Sort by priority descending, then index (FIFO)
            # To do this cleanly without destroying order, we find max
            # Best way to satisfy "Highest-priority, then FIFO" is:
            # Max priority is the primary sort.
            # We want the first occurrence of the max priority.
            
            best_job = max(self.jobs, key=lambda j: j.priority)
            
            # Wait, if multiple have same max priority, max() returns 
            # the first one it finds? 
            # "Highest-priority, FIFO order".
            # Let's just sort properly to be safe.
            # Actually, max() returns the first occurrence of the max value.
            # Since they are appended to the list, the first occurrence is the oldest.
            
            # Let's check:
            # jobs = [Job('a', {}, 1), Job('b', {}, 1)]
            # max(jobs, key=lambda j: j.priority) -> returns 'a' (the first).
            # This is correct.
            
            self.jobs.remove(best_job)
            return (best_job.id, best_job.data)
    ```

    Wait, `get_next_job` should probably remove the item or just return it? Usually "get next" in a queue implies consumption.
    The prompt says "return the highest-priority job's (id, data)". It doesn't explicitly say "remove", but a Queue usually consumes. I'll remove it to behave like a Queue.

    Wait, the `process_job` method: "must USE RetryPolicy (compose, don't reimplement retry logic)".
    The `RetryPolicy.run` takes `fn` and `data`.
    The `process_job` takes `job_id` and `Processor`.

    Let's double check `RetryPolicy.run` signature:
    `run(self, fn: Callable, data) -> tuple[bool, int]`
    The prompt says `data`. Since `Job` has `data: dict`, it's `dict`.

    - `Job` dataclass: Yes.
    - `RetryPolicy` class:
        - `__init__(self, max_attempts: int = 4)`: Yes.
        - `run(self, fn: Callable, data) -> tuple[bool, int]`: Yes.
        - No sleep: Yes.
    - `JobQueue` class:
        - `add_job`: Yes.
        - `process_job`: Yes.
        - `get_next_job`: Yes.
    - Composition: Yes.
    - Standard Library only: Yes.
    - No explanations: Yes.

    One detail: `process_job` takes `job_id`. I need to find the job.
    One detail: `get_next_job` returns `tuple[str, dict] | None`.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Dict, Tuple, List, 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
parents = 0 # No, just use a simple counter
            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:
            # Find job in list
            job = None
            for j in self.jobs:
                if j.id == job_id:
                    job = j
                    break
            
            if not job:
                return False
            
            success, attempts = self.retry_policy.run(Processor, job.data)
            job.retries = attempts
            return success

        def get_next_job(self) -> tuple[str, dict] | None:
            if not self.jobs:
                return None
            
            # Highest priority, then FIFO (first in list)
            # max() returns first occurrence of max value.
            best_job = max(self.jobs, key=lambda j: j.priority)
            self.jobs.remove(best_job)
            return (best_job.id, best_job.data)
    ```

    Refinement on `process_job`: The prompt asks to "orchestrate Jobs using RetryPolicy".
    Should `process_job` remove the job from the queue?
    Usually, processing a job means it's done (or failed).
    However, the prompt doesn't say "remove". But `get_next_job` is usually a consumer.
    If `process_job` is called by ID, it's likely the user already "got" it or is targeting a specific one. I will leave it in the list unless successful? Actually, the prompt doesn't specify. I'll leave it in the list but updated.

    Wait, looking at the instructions again:
    `process_job(self, job_id: str, Processor: Callable) -> bool`
    `get_next_job(self) -> tuple[str, dict] | None`

    I'll stick to the simplest interpretation.

    Final check on `RetryPolicy.run`:
    `run(self, fn: Callable, data) -> tuple[bool, int]`
    The prompt says `data`. I'll make sure it's clear.

    Python versioning: `tuple[bool, int]` syntax is 3.9+. I'll use that.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Dict, Tuple, List, 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:
            target_job = None
            for j in self.jobs:
                if j.id == job_id:
                    target_job = j
                    break
            
            if target_job is None:
                return False
            
            success, attempts = self.retry_policy.run(Processor, target_job.data)
            target_job.retries = attempts
            return success

        def get_next_job(self) -> tuple[str, dict] | None:
            if not self.jobs:
                return None
            
            # max() returns the first occurrence of the max value.
            # Since we append to the list, the first occurrence is the oldest.
            best_job = max(self.jobs, key=lambda j: j.priority)
            self.jobs.remove(best_job)
            return (best_job.id, best_job.data)
    ```

    Wait, `max` complexity is O(n). For a "Queue", `heapq` is O(log n).
    But the prompt asks to refactor the provided logic (which I don't see, but usually these prompts imply a simple structure). Given "FIFO order within the same priority", a list `max` is perfectly valid.

    One small detail: `retries` in `Job` dataclass. `RetryPolicy.run` returns `attempts_made`. `process_job` sets `job.retries = attempts`. This matches.

    Double check: `retries` is a field in `Job`.
    `RetryPolicy` should not sleep. Correct.

    One thing about `tuple[bool, int] | None`. This is valid in Python 3.10+ (Union type `|`). To be safe for 3.9+, I could use `Optional[Tuple[str, dict]]]`. But the prompt used `|`, so I will use that.

    Wait, the prompt says: `get_next_job(self) -> tuple[str, dict] | None`.
    I will use exactly that.

    Final check on `JobQueue.add_job`: `priority` is an `int`.
    Final check on `JobQueue.process_job`: `Processor` is `Callable`.

    One minor thing: `data` in `RetryPolicy.run` is not typed in the prompt signature `run(self, fn: Callable, data)`. I'll just use `data`.

    ```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:
            job = next((j for j in self.jobs if j.id == job_id), None)
            if job is None:
                return False
            
            success, attempts = self.retry_policy.run(Processor, job.data)
            job.retries = attempts
            return success

        def get_next_job(self) -> tuple[str, dict] | None:
            if not self.jobs:
                return None
            
            # Find the job with the highest priority.
            # max() returns the first instance of the highest value (FIFO).
            best_job = max(self.jobs, key=lambda j: j.priority)
            self.jobs.remove(best_job)
            return (best_job.id, best_job.data)
    ```
    Wait, I used `list[Job]` and `tuple[str, dict]`. This requires 3.9+. The prompt's syntax `tuple[...]` suggests 3.9+.

    One last check: "Do NOT actually sleep."
    I'm not sleeping.

    Wait, `process_job` signature: `process_job(self, job_id: str, Processor: Callable) -> bool`.
    My `next(...)` logic finds the job by ID. This is correct.
</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:
        job = next((j for j in self.jobs if j.id == job_id), None)
        if job is None:
            return False
        
        success, attempts = self.retry_policy.run(Processor, job.data)
        job.retries = attempts
        return success

    def get_next_job(self) -> tuple[str, dict] | None:
        if not self.jobs:
            return None
        
        # max() returns the first occurrence of the maximum value.
        # Since jobs are appended, the first occurrence satisfies FIFO.
        best_job = max(self.jobs, key=lambda j: j.priority)
        self.jobs.remove(best_job)
        return (best_job.id, best_job.data)
```
</content>