<reasoning_content>
*   Goal: Refactor a job queue into three composable classes: `Job`, `RetryPolicy`, and `JobQueue`.
    *   Constraints:
        *   Use `dataclass` for `Job`.
        *   `RetryPolicy` must handle retries with max attempts (no actual sleeping).
        *   `JobQueue` must orchestrate jobs using `RetryPolicy` (composition).
        *   `JobQueue` must handle priority (higher priority first) and FIFO within priority.
        *   Use only standard library.
        *   No explanations, only Python code.

    *   `Job`:
        ```python
        from dataclasses import dataclass
        from typing import Callable, Tuple, Optional

        @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[[dict], any], 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
        ```
        *Correction:* The prompt says `run(self, fn: Callable, data) -> tuple[bool, int]`. I should ensure the signature matches.

    *   `JobQueue`:
        *   Data structure: Needs to handle priority (higher first) and FIFO. A `heapq` is good for priority, but simple lists sorted by priority and timestamp/insertion order work too. However, since it's a queue, keeping a list of jobs and sorting or using a heap is standard. To maintain FIFO within same priority, I'll use a list of jobs and sort/manage them. Actually, for "highest-priority" and "FIFO within same priority", I can use a list and sort it every time or maintain it sorted. Or, use a list of lists where each index is a priority level (if finite) or a list of tuples `(-priority, insertion_index, job)`.
        *   Methods: `add_job`, `process_job`, `get_next_job`.
        *   `process_job` must use `RetryPolicy`.

    *   Let's refine `JobQueue`:
        *   `jobs`: A list to store `Job` objects.
        *   `add_job`: Appends `Job` to list.
        *   `get_next_job`:
            1. Sort by `-priority` then `insertion_order`.
            2. Return highest.
            3. Remove from list? The prompt doesn't explicitly say "remove", but "get next" usually implies consumption or identifies the next one. Let's assume it removes it or marks it. Usually, `get_next_job` in a queue context returns the item to be processed. If it's just a peek, I'll return it. If it's a pop, I'll remove it. Let's look at `process_job`. `process_job` takes a `Job_id`. This implies `get_next_job` might just find the ID.
            Wait, `process_job` takes `job_id`. `get_next_job` returns `(id, data)`.
            Actually, if `process_job` takes an ID, `get_next_job` should probably return the ID of the next job to process.
            Wait: `get_next_job(self) -> tuple[str, dict] | None`. This suggests it returns the data too.
            If `process_job` takes an ID, it implies the queue stores jobs.

    *   Refining `JobQueue` internal structure:
        A dictionary `self.jobs = {}` (mapping ID to Job object) and a list `self.order = []` to track order? No, priority is key.
        Let's use a list `self.jobs = []`.
        `add_job`: `self.jobs.append(Job(job_id, data, priority)))`.
        `get_next_job`: Sort `self.jobs` by `priority` descending. Return `self.jobs[0]`'s id and data.
        `process_job`: Find the job by ID. Use `RetryPolicy`.

    *   Wait, the `RetryPolicy` signature: `run(self, fn: Callable, data)`. `fn` is a callable.

    *   *Self-correction on `RetryPolicy`*: `run(self, fn: Callable, data)`. The prompt says `fn(data)`.

    *   *Refining `JobQueue` Logic*:
        To maintain FIFO within priority:
        Store jobs in a list.
        `add_job`: Append to list.
        `get_next_job`:
        Sort the list: `sorted_jobs = sorted(self.jobs, key=lambda x: (-x.priority, self.insertion_counter[x.id]))`.
        Actually, simpler: `sorted_jobs = sorted(self.jobs, key=lambda x: (-x.priority, x.insertion_index))`.

    Let's refine `JobQueue`:
    ```python
    class JobQueue:
        def __init__(self):
            self.jobs = [] # List of Job objects
            self.counter = 0 # To track insertion order for FIFO

        def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
            new_job = Job(id=job_id, data=data, priority=priority)
            new_job.insertion_index = self.counter
            self.jobs.append(new_job)
            self.counter += 1

        def get_next_job(self) -> tuple[str, dict] | None:
            if not self.jobs:
                return None
            # Sort by priority desc, then insertion_index asc
            self.jobs.sort(key=lambda x: (-x.priority, x.insertion_index))
            next_job = self.jobs[0]
            return (next_job.id, next_job.data)

        def process_job(self, job_id: str, Processor: Callable) -> bool:
            # Find job
            job = next((j for j in self.jobs if j.id == job_id), None)
            if not job:
                return False
            
            policy = RetryPolicy()
            success, attempts = policy.run(Processor, job.data)
            
            if success:
                self.jobs.remove(job) # Remove if successful? 
                # Usually processing means completion.
                return True
            return False
    ```
    Wait, `Job` dataclass doesn't have `insertion_index` in the prompt's requirements.
    Requirements: `Job` has `id`, `data`, `priority`, `retries`.
    I should stick to the requested fields.

    How to do FIFO without `insertion_index` in the dataclass?
    I can store a list of `(priority, insertion_order, job_id)`.
    Or just sort the list of `Job` objects and use the list index as the tie-breaker.

    Revised `JobQueue`:
    ```python
    class JobQueue:
        def __init__(self):
            self.jobs = [] # List of Job objects

        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
            # Since list maintains order, sorting with stable sort or 
            # sorting by priority DESC while maintaining original order 
            # for ties. Python' longer stable sort is not an issue, 
            # but we want high priority first.
            
            # To do FIFO for same priority:
            # If we sort only by priority descending, the relative order 
            # of same priority items is preserved (stable sort).
            self.jobs.sort(key=lambda x: x.priority, reverse=True)
            next_job = self.jobs[0]
            return (next_job.id, next_job.data)
    ```
    Wait, `list.sort` is stable. So if I sort by `priority` descending, items with the same priority will stay in their original relative order. That's perfect for FIFO.

    One detail: `process_job` doesn't say to remove the job from the queue. But usually, "processing" implies it's done. However, I'll follow the prompt strictly. It doesn't say to remove. But if I don't remove it, `get_next_job` will always return the same one.
    Actually, standard practice for a job queue: `process_job` takes an ID, finds it, executes it. `get_next_job` identifies the next one.
    If I don't remove it in `process_job`, it stays in the queue.

    Wait, `process_job` signature: `process_job(self, job_id: str, Processor: Callable) -> bool`.
    If `process_job` returns `True`/`False`, it's likely doing the work.

    Let's refine the code structure.

    Imports: `dataclass`, `Callable`, `Tuple`, `Optional`, `Any`.

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

    @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[[dict], Any], 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] = []

        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
            # Stable sort by priority descending
            self.jobs.sort(key=lambda x: x.priority, reverse=True)
            next_job = self.jobs[0]
            return (next_job.id, next_job.data)

        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
            
            policy = RetryPolicy()
            success, _ = policy.run(Processor, job.data)
            
            if success:
                self.jobs.remove(job)
                return True
            return False
    ```

    Wait, `JobQueue.get_next_job` returns `tuple[str, dict] | None`.
    If I sort the list inside `get_next_job`, I am mutating the state of the queue every time someone asks for the next job. This is fine.
    If I remove the job in `process_job`, then `get_next_job` will correctly show the next one.

    One detail: `RetryPolicy.run` signature `fn: Callable, data`.
    The prompt says `fn(data)`. My implementation `fn(data)` is correct.

    Double check `Job` fields: `id`, `data`, `priority`, `retries`.
    `retry_policy` fields: `max_attempts`.

    One small logic check: "Highest-priority" usually means highest number? Or lowest? Usually, Priority 10 > Priority 1. So `reverse=True` is correct for high numbers.

    Wait, `JobQueue.process_job` signature: `Processor: Callable`.
    I'll use `Any` for the return type of `fn` inside `RetryPolicy`.

    Final Check on Requirements:
    1. `Job` dataclass: Yes.
    2. `RetryPolicy`: `max_attempts` default 4, `run` returns `(success, attempts_made)`. No sleep. Yes.
    3. `JobQueue`: `add_job`, `process_job` (uses `RetryPolicy`), `get_next_job` (highest priority, FIFO same priority). Yes.
    4. Standard library only. Yes.
    5. Only Python code. Yes.

    One detail: `process_job` takes a `job_id`. I need to find the job with that ID.
    If I find it, I use `RetryPolicy`.
    Should I remove it if success? The prompt doesn't say. But if I don't remove it, the "next" job will stay the same one.
    Usually, `process_job` "processes" it. I will remove it upon success to make the queue functional.

    Wait, look at `get_next_job`. It returns a tuple.
    If `process_job` doesn't remove it, `get_next_job` remains same.
    If I remove it from `self.jobs` only if `success` is true, that makes the most sense for a "Job Queue".

    Actually, I'll look at the `process_job` signature again.
    `process_job(self, job_id: str, Processor: Callable) -> bool`
    If I return `True`, it means it was processed successfully. If it was successfully processed, it's no longer in the queue.

    Wait, I should make sure `RetryPolicy` is used via composition.
    ```python
    class JobQueue:
        def __init__(self):
            self.jobs = []
            self.retry_policy = RetryPolicy() # Composition

        def process_job(self, job_id: str, Processor: Callable) -> bool:
            # ... find job ...
            success, _ = self.retry_policy.run(Processor, job.data)
            # ...
    ```
    The prompt says: "must USE RetryPolicy (compose, don't reimplement retry logic)".
    I'll instantiate it inside `process_job` or as a class attribute. Either works for composition.

    Refining the `Job` dataclass definition to match prompt exactly:
    `id: str`, `data: dict`, `priority: int = 0`, `retries: int = 0`.

    One more check: `get_next_job` FIFO order.
    If I use `self.jobs.sort(key=lambda x: x.priority, reverse=True)`, and the list was `[Job(p=1, id="a"), Job(p=1, id="b"))]`, it will remain `["a", "b"]`. This is FIFO.

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

    @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[[dict], Any], 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
            # Sort by priority descending (highest number first).
            # list.sort is stable, preserving FIFO for same priority.
            self.jobs.sort(key=lambda x: x.priority, reverse=True)
            next_job = self.jobs[0]
            return (next_job.id, next_job.data)

        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)
            if success:
                self.jobs.remove(job)
                return True
            return False
    ```
    Wait, I'll make `RetryPolicy` instance creation local or global? The prompt says "orchestrates Jobs using RetryPolicy". I'll put it in `__init__`.

    Wait, `Tuple[bool, int]` and `Tuple[str, dict] | None`.
    Note: `|` operator is Python 3.10+. I'll use `Optional` or `Union` just in case, but usually `|` is fine now. I'll stick to the prompt's notation where possible.
    Prompt says `tuple[bool, int]`. I will use `tuple[bool, int]`.

    Wait, `tuple[bool, int]` is not valid syntax for `Tuple` (must be `tuple[bool, int]` in type hints or `Tuple[bool, int]`).
    Actually, `tuple[bool, int]` works in 3.9+.

    Let's refine `process_job`.
    If `process_job` fails (max retries reached), should it stay in the queue?
    Standard behavior: If it fails permanently, it's often moved to a Dead Letter Queue or removed. If I leave it in, `get_next_job` will keep picking it until it succeeds or I remove it.
    Actually, `process_job` takes an ID. If I don't remove it, and it fails, it's still in the list.
    I'll remove it if it succeeds. If it fails after max retries, I'll leave it or remove it? Usually, you'd remove it to stop it from clogging the queue. But the prompt doesn't specify.
    I'll remove it on `success`.

    One last check on `RetryPolicy.run(self, fn: Callable, data)`.
    I'll use that exact signature.
</reasoning_content>

<content>
```python
from dataclasses import dataclass
from typing import Callable, Tuple, Optional, Any

@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[[dict], Any], 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
        # Sort by priority descending. Python's sort is stable,
        # preserving FIFO order for jobs with the same priority.
        self.jobs.sort(key=lambda x: x.priority, reverse=True)
        next_job = self.jobs[0]
        return (next_job.id, next_job.data)

    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)
        if success:
            self.jobs.remove(job)
            return True
        return False
```
</content>