<reasoning_content>
*   Refactor a job queue into three composable classes: `Job`, `RetryPolicy`, and `JobQueue`.
    *   Environment: Python (standard library only).
    *   Output: Only the Python code, no explanations.

        *   Type: `dataclass`.
        *   Fields: `id: str`, `data: dict`, `priority: int = 0`, `retries: int = 0`.

        *   Init: `max_attempts: int = 4`.
        *   Method: `run(self, fn: Callable, data: dict) -> tuple[bool, int]`.
        *   Behavior: Calls `fn(data)`, retries on exception until success or `max_attempts` reached. No `time.sleep`. Return `(success, attempts_made)`.

        *   Methods:
            *   `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`.
        *   Logic: `JobQueue` uses `RetryPolicy`. `get_next_job` must be highest priority first, then FIFO.

    *   `Job` dataclass:
        ```python
        from dataclasses import dataclass
        from typing import Callable, Dict, 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, 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: Need a way to store jobs. A list or a priority queue? Since "FIFO within the same priority", a sorted list or a heap where priority is negated (or handled properly) works. However, simple list sorting/manipulating or a list of lists works too.
        *   `add_job`: Instantiate `Job` and store it.
        *   `get_next_job`: Find highest priority, then oldest (FIFO).
        *   `process_job`: Must use `RetryPolicy`.

    *   *Refining `JobQueue` storage*:
        To handle "highest priority" and "FIFO", we can store jobs in a list. When `get_next_job` is called, find the max priority.
        Wait, the requirement says "FIFO order within the same priority".
        Maybe just a list of jobs. `get_next_job` searches for the highest priority.

    *   *Refining `process_job`*:
        It needs to find the job by ID, use `RetryPolicy` to run the processor.

    *   *Requirement Check*: "Use only the standard library".

    ```python
    from dataclasses import dataclass
    from typing import Callable, Dict, Tuple, Optional, List

    @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:
            new_job = Job(id=job_id, data=data, priority=priority)
            self.jobs.append(new_job)

        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 job in self.jobs:
                if job.priority == max_priority:
                    # We need to return the data, but usually we'd pop it? 
                    # The prompt says "return the highest-priority job's (id, data)"
                    # It doesn't say "remove it". But usually queues consume.
                    # Let's check wording: "return the highest-priority job's (id, data)".
                    # I'll keep it in the list unless I see a reason to pop.
                    # Actually, if I don't pop it, I can't "process" it properly 
                    # in a standard queue sense, but the prompt doesn't say pop.
                    # Wait, usually "get_next_job" implies taking it out.
                    # However, I'll just find it.
                    return (job.id, job.data)
            return None

        def process_job(self, job_id: str, Processor: Callable) -> bool:
            # Find the job
            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 it failed, we might want to increment retries? 
            # The prompt doesn't specify what happens to the job object 
            # after processing, just that we must use RetryPolicy.
            return success
    ```

    *Re-reading*: "return the highest-priority job's (id, data)".
    If I don't remove it, calling `get_next_job` repeatedly returns the same job. That is technically what the prompt asks for.
    However, most "Queues" remove the item. But I will stick strictly to the prompt.

    *Refining `get_next_job`*:
    To be safe with FIFO, I'll iterate and pick the first one that matches the max priority.

    *Refining `RetryPolicy`*:
    The prompt says: `run(self, fn: Callable, data) -> tuple[bool, int]`.
    The signature `data` doesn't have a type hint in the prompt text (`data`), but the fields are `data: dict`. I'll use `dict`.

    *Wait, `JobQueue` logic*:
    If `get_next_job` is meant to behave like a queue, it should remove the item. But usually "get" vs "poll". I'll just return it.
    Actually, if I call `process_job`, does it remove the job? The prompt doesn't say.
    I will implement `get_next_job` to return the tuple and `process_job` to run it.

    *One detail*: `Job` has a `retries` field. `RetryPolicy` doesn't update it in the provided requirements. The prompt says "Do NOT actually sleep" and "must USE RetryPolicy (compose, don't reimplement retry logic)".
    Since `RetryPolicy` is a separate class, and I shouldn't reimplement logic, I just call it.

    *Typing*: Use `typing.Callable`, `typing.Tuple`, `typing.Optional`, `typing.Dict`.

    *Standard Library only*: Yes.

    *Check*: `JobQueue.process_job` - I need to find the job. I'll use the `job_id`.

    *Double check "FIFO order within the same priority"*:
    Example:
    Job A (p=1)
    Job B (p=2)
    Job C (p=2)
    `get_next_job` should return B.
    If B is removed, next call returns C.
    If B is NOT removed, next call returns B again.
    Usually, a "Job Queue" removes items. I'll implement it so that `get_next_job` removes the item from the internal storage to make it a functional queue. If I don't remove it, it's a "Peek".
    The prompt says "return the highest-priority...". I'll assume "get" implies "remove" in a Queue context, but to be safe and strictly follow the prompt "return the... (id, data)", I will just find it.
    Actually, looking at standard patterns, `get_next_job` usually removes. I'll remove it.

    *Wait*, if I remove it in `get_next_job`, then `process_job` (which takes a `job_id`) won't find it anymore.
    This implies `process_job` should probably be the one that removes it, OR `get_next_job` doesn't remove it.
    Let's look at the methods again:
    1. `add_job`
    2. `process_job(job_id, Processor)`
    3. `get_next_job()`
    
    If `process_job` is called by `job_id`, then `get_next_job` must not remove the job, otherwise `process_job` would fail if called after `get_next_job`.
    Therefore: `get_next_job` finds and returns, `process_job` executes and removes? Or `process_job` just executes.
    I'll stick to: `get_next_job` finds the highest priority, FIFO, and returns. `process_job` executes the logic.

    *Refined Logic*:
    `get_next_job`:
    1. Find max priority among all jobs in the list.
    2. Find the first job in the list with that priority.
    3. Return `(id, data)`.

    `process_job`:
    1. Find job by ID.
    2. Use `RetryPolicy` to run `Processor`.
    3. Return boolean success.

    One more thing: `retries` field in `Job`. Should I update it? The prompt doesn't say. I'll leave it.

    Wait, I'll make sure `process_job` handles the case where `job_id` is not found.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Dict, Tuple, Optional, List

    @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) -> Optional[Tuple[str, dict]] :
            if not self.jobs:
                return None
            
            max_priority = max(j.priority for j in self.jobs)
            for j in self.jobs:
                if j.priority == max_priority:
                    return (j.id, j.data)
            return None

        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
    ```

    Wait, I'll use `tuple[bool, int]` style for Python 3.9+ as requested in the prompt's type hints.

    Actually, the prompt uses `tuple[bool, int]` which is 3.9+ syntax. I will use that.

    One detail: `JobQueue.process_job` signature is `(self, job_id: str, Processor: Callable) -> bool`.
    I'll use the exact parameter name `Processor`.

    One detail: "Do NOT actually sleep". Clear.
    "FIFO order within the same priority". My `for j in self.jobs` check ensures FIFO because I append to the end and iterate from the start.

    Final Polish:
    - dataclass `Job`
    - `RetryPolicy` with `run`
    - `JobQueue` with `add_job`, `process_job`, `get_next_job`.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Dict, Tuple, Optional, List

    @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
            
            max_priority = max(job.priority for job in self.jobs)
            for job in self.jobs:
                if job.priority == max_priority:
                    return (job.id, job.data)
            return None

        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
    ```

    Double check `JobQueue.process_job`: the prompt says `Processor: Callable`. I used that.

    One final look at `RetryPolicy.run` return type: `tuple[bool, int]`.
    One final look at `JobQueue.get_next_job` return type: `tuple[str, dict] | None`.

    The prompt asks for `JobQueue.process_job` to use `RetryPolicy`. I initialized it in `__init__` and called it in `process_job`.

    Wait, `JobQueue.get_next_job` return type `tuple[str, dict] | None`.
    I'll use `Optional[tuple[str, dict]]` or just the `|` syntax. Since the prompt used `|`, I'll use that.

    Actually, I'll stick to the prompt's exact syntax where possible.

    One small fix: `self.jobs.append(Job(id=job_id, data=data, priority=priority)))`
    Wait, `priority` is a field in `Job`, so `priority=priority` is correct.

    Final check on "FIFO order within the same priority".
    If I have:
    0: Job1 (p=1)
    1: Job2 (p=2)
    2: Job3 (p=2)
    `max_priority` is 2.
    Loop starts:
    Index 0: priority 1 != 2
    Index 1: priority 2 == 2 -> Returns Job2.
    Correct.

    Wait, I'll provide the solution.

    One minor point: The prompt says `data` in `run(self, fn: Callable, data)`. I'll write `data: dict` or just `data`? To be safe, I'll match the prompt's signature exactly where it's explicit, but usually `data` without type hint in the prompt means I can type hint it or not. I'll include `dict` to be robust.

    Actually, the prompt's signature for `RetryPolicy.run` is `run(self, fn: Callable, data)`. It doesn't have a type for `data`. But `Job` has `data: dict`.

    Let me re-read:
    `run(self, fn: Callable, data) -> tuple[bool, int]`
    I will use `data` without a colon type hint if it's not there, or just `data: dict`. I'll go with `data: dict` to be safe as it's standard. No, the prompt text has `data` without a colon. I'll just omit the type hint for that specific parameter to be strictly compliant. No, actually most users prefer type hints. I'll provide it as `data: dict`.

    Wait, I'll use `tuple[bool, int]` for `run`.

    Let's re-verify the `Job` dataclass fields.
    `id: str`, `data: dict`, `priority: int = 0`, `retries: int = 0`. Correct.
</reasoning_content>

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

@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:
        new_job = Job(id=job_id, data=data, priority=priority)
        self.jobs.append(new_job)

    def get_next_job(self) -> tuple[str, dict] | None:
        if not self.jobs:
            return None
        
        max_priority = max(job.priority for job in self.jobs)
        for job in self.jobs:
            if job.priority == max_priority:
                return (job.id, job.data)
        return None

    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
```
</content>