<reasoning_content>
*   Refactor a job queue into three composable classes: `Job`, `RetryPolicy`, and `JobQueue`.
    *   `Job` (dataclass): `id` (str), `data` (dict), `priority` (int), `retries` (int).
    *   `RetryPolicy`:
        *   `__init__(max_attempts: int = 4)`.
        *   `run(fn: Callable, data) -> tuple[bool, int]`: Execute `fn(data)`, retry on exception until success or limit reached. No actual sleep.
    *   `JobQueue`:
        *   `add_job(job_id, data, priority)`.
        *   `process_job(job_id, processor)`: Use `RetryPolicy`.
        *   `get_next_job()`: Highest priority first, then FIFO for same priority.
    *   Constraints: Use standard library only. No external packages. Only Python code, no explanations.

    *   `from dataclasses import dataclass`
    *   `from typing import Callable, Dict, Tuple, List, Optional`
    *   `from collections import deque` (for FIFO)
    *   `import heapq` (for priority queue) or just sorting a list. Given "highest priority... FIFO order", `heapq` is good, but we need to handle the same priority correctly.

    *   `Job` dataclass.
    *   `RetryPolicy` class.
    *   `JobQueue` class.

    *   `Job`:
        ```python
        @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 way to store jobs. A list or a priority queue.
        *   To handle priority + FIFO: Use a tuple `(-priority, counter, job_id)`. Or just sort. Since `get_next_job` needs to return data, let's store `Job` objects.
        *   Requirement: `add_job(job_id, data, priority)`.
        *   Requirement: `process_job(job_id, processor)`.
        *   Requirement: `get_next_job()`.

    *   *Self-correction on `JobQueue`*:
        The `Job` dataclass has a `retries` field. `RetryPolicy` also tracks attempts. The prompt says `process_job` must *use* `RetryPolicy`.
        `JobQueue` needs to store jobs. Let's use a list and sort it or use `heapq`.
        Wait, the prompt says `get_next_job` returns `tuple[str, dict]`.
        The `process_job` method takes `job_id`. This implies `JobQueue` needs a way to look up a job by ID.

    *   *Refined `JobQueue` structure*:
        *   `self.jobs`: A dictionary `id -> Job`.
        *   `self.priority_queue`: A list to store order.
        *   To maintain FIFO for same priority: `( -priority, sequence_number, job_id )`.

    *   *Actually, let's keep it simpler*:
        `self.jobs`: List of `Job` objects.
        `add_job`: Append to list.
        `get_next_job`: Sort list by priority (desc) and then by order of insertion (which is natural if we use a list). Actually, `heapq` is better for performance, but a simple list works for a small task.
        Wait, `process_job` needs to find the job by ID.

    *   *Proposed `JobQueue` methods*:
        1. `add_job(job_id, data, priority)`:
           Create `Job` object. Add to a storage (dict or list).
        2. `get_next_job()`:
            Sort internal storage by priority descending. Take first. Remove it.
        3. `process_job(job_id, processor)`:
            Find job by id. Use `RetryPolicy` instance to run `processor` on `job.data`. Update `job.retries`? The prompt doesn't specify how to update `retries`, but usually, `RetryPolicy` handles the loop.

    *   *Wait, looking at `process_job` signature*:
        `process_job(self, job_id: str, processor: Callable) -> bool`.
        It needs a `RetryPolicy` instance. Should `JobQueue` have one? Yes, "orchestrates Jobs using RetryPolicy".

    *   *Refining `JobQueue` Storage*:
        Let's use a dictionary `self.jobs` for $O(1)$ lookup by ID (for `process_job`) and a list `self.queue` to track order/priority for `get_next_job`.
        Wait, `get_next_job` removes the job.

        Actually, a simple list of `Job` objects works well.
        `add_job`: `self.jobs.append(Job(...))`
        `get_next_job`: `sorted_jobs = sorted(self.jobs, key=lambda x: (-x.priority, self.insertion_counter[x.id]))` - this is getting complex.

        Let's stick to:
        `self.jobs`: List of `Job` objects.
        `add_job`: `self.jobs.append(Job(...))`
        `get_next_job`:
           Sort `self.jobs` by priority (descending) and then by the order they were added.
           To preserve order for same priority, we can use a sequence counter.
        `process_job`:
           Find `Job` in `self.jobs` by `id`.
           Run `retry_policy.run(processor, job.data)`.

    *   *Wait, `RetryPolicy` doesn't take the `Job` object, just the `data`*.
        `process_job` returns `bool` (success/failure).

    *   *Let's look at `Job` fields again*: `id`, `data`, `priority`, `retries`.
        `RetryPolicy.run` returns `(success, attempts_made)`.
        Maybe `process_job` updates `job.retries`? The prompt doesn't explicitly say to update it, but it's a field in `Job`. I'll just update it for completeness if successful/attempted.

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

            def add_job(self, job_id, data, priority=0):
                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. Stability of sort handles FIFO.
                self.jobs.sort(key=lambda x: x.priority, reverse=True)
                job = self.jobs.pop(0)
                return (job.id, job.data)

            def process_job(self, job_id, processor):
                # Find the job. Note: get_next_job removes it,
                # but process_job takes an ID. This implies the job
                # is still in the queue.
                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
        ```

    *   *Wait*, `process_job` might be called on a job that hasn't been "popped" yet.
        The prompt says `get_next_job` returns the highest priority job.
        If `process_job` is called, does it remove it? The prompt doesn't say. I'll assume it stays in the list unless `get_next_job` is called.

    *   *Wait*, `Job` has a `retries` field. If `RetryPolicy` returns `attempts_made`, I should probably update `job.retries`.

    *   *Final Check on `RetryPolicy`*:
        `run(self, fn: Callable, data) -> tuple[bool, int]`
        "calls `fn(data)`, retries on any exception until success or `max_attempts` reached".

    *   *Final Check on `JobQueue` requirements*:
        `add_job(job_id, data, priority)`
        `process_job(job_id, processor)`
        `get_next_job()` -> `(id, data)` or `None`.

    One detail: `get_next_job` says "FIFO order within the same priority".
    Python's `sort` is stable. So if I sort by priority descending, the relative order of jobs with the same priority (the order they were appended) will be preserved.

    *   *Implementation Detail*: `Jab` uses `@dataclass`.
    *   *Implementation Detail*: `RetryPolicy` takes `max_attempts=4`.

    ```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 job is None:
                return False
            success, attempts = self.retry_policy.run(processor, job.data)
            job.retries = attempts
            return success

        def get_next_job(self) -> Optional[Tuple[str, dict]] :
            if not self.jobs:
                return None
            # Stable sort: priority descending.
            self.jobs.sort(key=lambda x: x.priority, reverse=True)
            job = self.jobs.pop(0)
            return (job.id, job.data)
    ```

    Wait, the `Job` dataclass has a `retries` field. `RetryPolicy.run` returns `attempts_made`. I should probably update `job.retries` in `process_job`.

    One edge case: `get_next_job` says "FIFO order within same priority".
    If I use `sort` every time, it works.
    Actually, `self.jobs.sort(key=lambda x: x.priority, reverse=True)` is correct because Python's sort is stable.

    Actually, should `process_job` remove the job? The prompt doesn't say. Usually, `process_job` is an action on a specific job. `get_next_job` is a retrieval.

    Double check `RetryPolicy` requirements:
    "calls `fn(data)`, retries on any exception until success or `max_attempts` reached, returns `(success, attempts_made)`."

    Let's refine the `JobQueue` to be cleaner.

    ```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 job is None:
                return False
            success, attempts = self.retry_policy.run(processor, job.data)
            job.retries = attempts
            return success

        def get_next_job(self) -> Optional[Tuple[str, dict]] :
            if not self.jobs:
                return None
            # Python's sort is stable, so FIFO is preserved 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.get_next_job` says "return the highest-priority job's (id, data)".
    If I use `sort` every time, it might be slightly inefficient ($O(N \log N)$), but it's the most straightforward way to ensure stability without a counter. Given the context, it's perfectly fine.

    Wait, I just noticed `Job` dataclass `priority` is `int = 0`.
    I should make sure the types match exactly.

    One small check: `Callable` needs to be imported from `typing`.

    Wait, the prompt says `priority` is an `int`. If priority is higher value = higher priority? Usually yes. `reverse=True` makes highest value come first.

    One final look: "returns `(success, attempts_made)`".
    "Do NOT actually sleep."
    "Use only the standard library".

    Code looks solid.

    One minor thing: `JobQueue.process_job`... should it remove the job from the queue? The instructions don't say to remove it. `get_next_job` removes it. I will leave `process_job` without removal to be safe, as it's specifically asked to "process" a job by ID.

    Wait, if `process_job` is called on an ID, and that job was already popped by `get_next_job`, it should return `False`. My `next(...)` logic handles that.

    One more thing: The prompt says "Write only the Python code, no explanations."

    Final check on `RetryPolicy.run`:
    `attempts` starts at 0.
    Loop 1: `attempts` < 4. `fn` fails. `attempts` becomes 1.
    Loop 2: `attempts` < 4. `fn` fails. `attempts` becomes 2.
    Loop 3: `attempts` < 4. `fn` fails. `attempts` becomes 3.
    Loop 4: `attempts` < 4. `fn` fails. `attempts` becomes 4.
    Loop 5: `attempts` < 4 is false.
    Returns `(False, 4)`. Correct.
    If success on first try: returns `(True, 1)`. Correct.

    One thing: `Job` dataclass field names.
    `id`, `data`, `priority`, `retries`.
    Wait, I wrote `priority` in the dataclass but `priority` in the function. I must ensure it's `priority`. The prompt says `priority`, not `priority`. Oh, there's a typo in the prompt's description: `priority:` vs `priority:`. I will use `priority`.

    Prompt: `priority: int = 0` (There is a colon after priority? No, `priority: int = 0` looks like a typo for `priority: int = 0`). Actually, it's `priority: int = 0`. No, looking closer: `priority:, int = 0`. Wait, let me re-read.
    "priority:, int = 0"
    Actually, it's `priority: int = 0`.
    The prompt says: `priority:, int = 0`.
    Wait, let me look again: `priority:, int = 0`. There is a comma? No, it's a colon.
    `priority: int = 0`.
    Ah, I see. It says `priority:, int = 0`. Wait, no, looking at the raw text:
    `priority:, int = 0`. Actually, looking at the source text provided: `priority:, int = 0`.
    Let me re-examine: `priority:, int = 0`.
    Wait, it might be `priority: int = 0`. It's hard to tell if it's a comma or colon in some fonts.
    I'll assume it's a typo for `priority: int = 0`.
    Let me check the `retries` field too. `retries:, int = 0`.
    Actually, looking at the prompt again: `priority:, int = 0`.
    Wait, I will use `priority: int = 0` and `retries: int = 0` because `priority:,` is syntactically invalid for a dataclass field. It's almost certainly a colon.

    Let me double check the prompt string again.
    `priority:, int = 0`
    `retries:, int = 0`
    It really looks like a comma. But `dataclass` fields can't have commas in the name.
    Wait, I see it now. `priority:,`... no, it's `priority:`.
    Okay, I will use `priority: int = 0`.

    Wait, `JobQueue.process_job` argument: `processor: Callable`.
    `RetryPolicy.run` argument: `fn: Callable`.

    I will write the code now.

    One detail: `JobQueue.get_next_job` signature: `tuple[str, dict] | None`.
    I will use `Optional[Tuple[str, dict]]` or `Tuple[str, dict] | None`.
    Since Python 3.10+ is standard now, I'll use the `|` syntax or just `Optional`. Standard library only, so I'll use `Optional` to be safe for older versions, or just the `|` if I'm sure. The prompt uses `|` in the `get_next_job` definition: `tuple[str, dict] | None`. I will follow that style.

    Wait, the prompt says: `process_job(self, job_id: str, processor:) -> bool`.
    It looks like `job_id:` and `processor:`.
    I'll use standard type hinting.
</reasoning_content>

<content>
```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 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
        
        # Python's sort is stable, preserving FIFO order for items with same priority
        self.jobs.sort(key=lambda x: x.priority, reverse=True)
        job = self.jobs.pop(0)
        return (job.id, job.data)
```
</content>