<reasoning_content>
*   Refactor a job queue into three specific classes in `solution.py`.
    *   Classes: `Job`, `RetryPolicy`, `JobQueue`.
    *   `Job`: Dataclass with `id`, `data`, `priority`, `retries`.
    *   `RetryPolicy`: Handles retries with a `max_attempts` limit. `run` method calls a function, catches exceptions, and returns `(success, attempts_made)`. No actual sleeping.
    *   `JobQueue`: Manages jobs. `add_job` adds a job, `process_job` uses `RetryPolicy`, `get_next_job` returns the highest priority job (FIFO for same priority).
    *   Constraints: Standard library only. Only Python code, 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_made = 0
                while attempts_made < self.max_attempts:
                    try:
                        fn(data)
                        return True, attempts_made + 1
                    except Exception:
                        attempts_made += 1
                return False, attempts_made
        ```
        *Refinement:* The prompt says "retries on any exception until success or max_attempts reached". If it fails all attempts, it returns `(False, max_attempts)`.

    *   `JobQueue`:
        ```python
        import heapq
        from collections import deque

        class JobQueue:
            def __init__(self):
                self.jobs = [] # Use a heap? Or a sorted list?
                self.retry_policy = RetryPolicy()

            def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
                # Priority: higher is better or lower? Usually, higher value = higher priority or lower?
                # Prompt says "highest-priority job". Usually, higher number = higher priority.
                # "FIFO order within the same priority".
                # heapq is a min-heap. To use max priority as min, use -priority.
                # Or just keep a list and sort. But heapq is better for performance.
                # Actually, for "FIFO order within same priority", I need a counter.
                pass

            def process_job(self, job_id: str, processor: Callable) -> bool:
                # Find job, run through policy, update retries?
                pass

            def get_next_job(self) -> tuple[str, dict] | None:
                pass
        ```

    *   *Implementation detail for JobQueue*:
        To satisfy "highest-priority job" and "FIFO order within the same priority":
        I'll use a heap. Elements: `(-priority, insertion_index, job_id, data)`.
        Actually, the prompt asks to store `Job` objects? No, `add_job` takes params, but the `Job` class is defined. I should store `Job` objects.

        Wait, `process_job` takes `job_id`. I need a way to find the job by ID.
        Let's store a dictionary of `id -> Job` and a list/heap for ordering.

        Actually, `get_next_job` returns `(id, data)`.

        Let's re-read: `add_job(self, job_id: str, data: dict, priority: int = 0) -> None`.
        I should probably store the `Job` objects in a way that supports sorting by priority (desc) and then order of addition.

        `JobQueue` structure:
        - `self.jobs`: A list of `Job` objects?
        - `self.counter`: To track insertion order for FIFO.
        - `self.retry_policy`: Instance of `RetryPolicy`.

        `add_job`:
        - Create `Job` object.
        - Store in a list or heap.

        `get_next_job`:
        - Sort by `-priority` then `counter`.
        - Pop the best one.

        `process_job`:
        - Find job by ID.
        - Call `retry_policy.run(processor, job.data)`.
        - Return `success`.

    *   *Refining `RetryPolicy.run`*:
        ```python
        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
        ```

    *   *Refining `JobQueue`*:
        Since I need to find by ID for `process_job` and also get the highest priority for `get_next_job`, I'll maintain a dictionary `{id: Job}` and a list for sorting.

        Actually, if `get_next_job` is called, it's usually the one being popped. But `process_job` takes an ID. This implies the job stays in the queue or is handled separately?
        Usually, in these patterns, `process_job` might be called on a job already pulled or still in the queue.

        Wait, the prompt says: `get_next_job` returns the highest-priority job's `(id, data)`.
        And `process_job` takes `job_id`.

        Let's look at the logic:
        1. `add_job`: create Job object.
        2. `get_next_job`: find highest priority, remove it from the "available" pool? Or just peek? Usually "get" implies removal.
        3. `process_job`: take an ID. If it's not in the queue, is it still there?

        Let's assume:
        - `self.queue`: A list of `Job` objects.
        - `self.all_jobs`: A dict `{id: Job}` to find by ID easily.

        Actually, if `get_next_job` removes it, `process_job` might not find it if it's already "out". But `process_job` is usually called *on* a job that was retrieved.
        Wait, if `get_next_job` returns the data, but `process_job` takes the `job_id`, it implies the job is still "known" by the queue.

        Let's keep it simple:
        `self.jobs`: list of `Job` objects.
        `add_job`: append to list.
        `get_next_job`: sort list by `-priority`, then `index` (insertion order), pop the first one, return `(id, data)`.
        `process_job`: find job in `self.jobs` by id. If found, run policy.

        Wait, if `get_next_job` removes it, `process_job` won't find it.
        Perhaps `get_next_job` doesn't remove it? "Get" usually does, but "Peek" doesn't.
        However, if I don't remove it, `get_next_job` will always return the same highest priority job. That's a bug.
        So `get_next_job` *must* remove it.
        If `get_next_job` removes it, then `process_job` must be able to find it.
        Maybe `self.jobs` is a dictionary, and there's a separate priority structure.

        Let's refine:
        `self.jobs` = dictionary `{id: Job}`.
        `self.priority_queue` = heap of `(-priority, insertion_index, id)`.

        `add_job`:
        - Create Job.
        - `self.jobs[id] = job`.
        - `heapq.heappush(self.priority_queue, (-priority, counter, id))`.

        `get_next_job`:
        - `(-p, i, jid) = heapq.heappop(self.priority_queue)`.
        - `job = self.jobs.pop(jid)`.
        - return `(job.id, job.data)`.

        `process_job`:
        - This is tricky. If `get_next_job` already popped it, `process_job` won't find it in `self.jobs`.
        - Maybe `process_job` is meant to be called *before* `get_next_job`? No, that's not a queue.
        - Maybe `process_job` is called on a job that is still *in* the queue?
        - Let's check the signature: `process_job(self, job_id: str, processor: Callable) -> bool`.
        - If I process it, and it succeeds, do I remove it? Or is it removed by `get_next_job`?

        Let's re-read: "orchestrates Jobs using RetryPolicy".
        Usually, a queue flow is: `add` -> `get_next` -> `process`.
        If `get_next` removes it, then `process_job` needs to know about it.
        Maybe I should keep a `self.active_jobs` or `self.processed_jobs`?
        No, let's assume `process_job` is called on a job that is *still in the queue*.
        But if I call `get_next_job`, it's gone.

        Alternative Interpretation:
        `get_next_job` does NOT remove it. It just finds the best one.
        But then `get_next_job` would always return the same thing.
        Unless `process_job` removes it?
        "process_job ... returns bool".
        If `process_job` succeeds, maybe it removes it?

        Actually, common pattern:
        `get_next_job` retrieves it (removes from queue).
        `process_job` is then called on that retrieved job.
        But `process_job` takes an `id`.
        This implies `JobQueue` must hold the job even after it's "gotten".

        Let's try this:
        `self.jobs`: dictionary `{id: Job}`.
        `self.priority_queue`: heap.
        `get_next_job`:
        - `heapq.heappop` to get the `id`.
        - Return `(id, self.jobs[id].data)`.
        - Do NOT remove from `self.jobs`.
        `process_job`:
        - Find `job` in `self.jobs`.
        - If not found, return `False`.
        - Run policy.
        - If success, remove from `self.jobs`? Or leave it?
        - If I don't remove it from `self.jobs` in `process_job`, `get_next_job` will keep returning it because it's still in the heap? No, it's popped from the heap.

        Wait! If `get_next_job` pops from the heap, that specific instance of "highest priority" is gone from the queue.
        So `process_job` must be called *before* `get_next_job`? No.
        Maybe `process_job` is called, and if it succeeds, it's considered "done".

        Let's look at the prompt again.
        `get_next_job` returns the highest priority.
        `process_job` uses `RetryPolicy`.
        Let's assume the simplest linear flow:
        1. `add_job` (Job is added to a collection)
        2. `get_next_job` (Job is removed from the "pending" collection)
        3. `process_job` (Job is processed).

        Wait, if `get_next_job` removes it, `process_job` needs to see it.
        Maybe `JobQueue` stores "completed" jobs? No.
        Maybe `process_job` should be called *instead* of `get_next_job`?
        No, they are separate methods.

        Let's try this logic:
        `self.jobs`: Dictionary `{id: Job}`.
        `self.queue`: List of IDs sorted by priority/order.
        `add_job`: Add to dict, append to list, sort list.
        `get_next_job`: Pop from list, return `(id, data)`.
        `process_job`: Look up `id` in `self.jobs`. If found, run policy. If success, delete from `self.jobs`.

        Wait, if `get_next_job` pops it from the list, but `process_job` also needs to find it, then `self.jobs` must still have it.
        If `process_job` removes it from `self.jobs` upon success, then it's gone.

        One more possibility:
        `get_next_job` does NOT remove it. It just returns the best one.
        `process_job` DOES remove it from the queue (the heap/list).
        But `process_job` takes an `id`.
        So `process_job` needs to find the job in the priority structure, remove it, and then run it.
        This makes sense. `get_next_job` is a "peek" or a "peek-and-return" (but why would it return if it doesn't remove?).

        Actually, let's look at the most standard way to implement this for a coding test:
        - `self.jobs` = `{id: Job}`.
        - `self.heap` = `[(-priority, index, id)]`.
        - `add_job`: add to dict, push to heap.
        - `get_next_job`: pop from heap, return `(id, data)`. (Don't remove from dict).
        - `process_job`: find in dict. If success, remove from dict.

        Wait, if I don't remove from dict, and `get_next_job` is called multiple times, it will pop different items from the heap. This works!
        The only issue is: what happens if `process_job` is called on an ID that was already popped by `get_next_job`?
        Then `process_job` will still find it in the dictionary.
        Does this matter?
        If `process_job` succeeds, it removes it from the dictionary.
        If `get_next_job` is called again, and that ID was the highest priority, it's already gone from the heap.
        So it won't be returned again.
        This seems robust.

    *   *Wait*, "FIFO order within the same priority".
        I need a counter.

    *   *Refining `RetryPolicy`*:
        "Do NOT actually sleep."
        `run` method:
        ```python
        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
        ```

    *   *Refining `JobQueue`*:
        ```python
        import heapq
        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
            ...
        ```

    *   *Wait, the `Job` class has a `retries` field.*
        Should `process_job` increment `retries`?
        The `RetryPolicy` says "retries on any exception until success or max_attempts reached".
        It doesn't say to update the `Job` object's `retries` field.
        However, it's a field in the dataclass. Usually, if it's there, it should be used or updated.
        But `RetryPolicy` is a separate class. It doesn't know about `Job` objects, only `data` and a `Callable`.
        So `RetryPolicy` shouldn't touch `Job.retries`.
        Maybe `JobQueue.process_job` should?
        But `RetryPolicy.run` returns `attempts_made`.
        If `process_job` calls `run`, it can see how many attempts were made.
        Wait, the `Job` dataclass has `retries`. Maybe `add_job` should initialize it? Or `process_job` should update it?
        The instructions don't specify updating it. I'll stick to the requirements.

    *   *Let's double check `JobQueue.get_next_job`*:
        "return the highest-priority job's (id, data); FIFO order within the same priority."
        Highest priority = highest value.
        FIFO = earliest insertion.
        So I need `(-priority, insertion_index, id)`.

    *   *Implementation Details Check*:
        - `Job` dataclass: `id`, `data`, `priority`, `retries`.
        - `RetryPolicy`: `max_attempts`, `run(fn, data) -> (bool, int)`.
        - `JobQueue`: `add_job`, `process_job`, `get_next_job`.
        - `process_job` MUST use `RetryPolicy`.

    ```python
    from dataclasses import dataclass
    from typing import Callable
    import heapq

    @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 = {}  # id -> Job
            self.heap = []  # (-priority, index, id)
            self.counter = 0
            self.retry_policy = RetryPolicy()

        def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
            job = Job(id=job_id, data=data, priority=priority)
            self.jobs[job_id] = job
            heapq.heappush(self.heap, (-priority, self.counter, job_id)))
            self.counter += 1

        def process_job(self, job_id: str, processor: Callable) -> bool:
            job = self.jobs.get(job_id)
            if not job:
                return False
            
            success, _ = self.retry_policy.run(processor, job.data)
            if success:
                # If successful, we might want to remove it? 
                # The prompt doesn't say. But usually, successful jobs 
                # are removed. However, get_next_job also removes from heap.
                # If I remove it here, it's gone from jobs dict.
                del self.jobs[job_id]
                return True
            return False

        def get_next_job(self) -> tuple[str, dict] | None:
            if not self.heap:
                return None
            
            neg_priority, index, job_id = heapq.heappop(self.heap)
            job = self.jobs.get(job_id)
            if not job:
                # This might happen if process_job removed it
                return self.get_next_job()
            
            return (job.id, job.data)
    ```

    *Self-Correction on `process_job`*:
    If `process_job` is called on a job that was already "gotten" by `get_next_job`, it's still in `self.jobs` but NOT in `self.heap`.
    If `process_job` succeeds and deletes from `self.jobs`, then `get_next_job` (if called again) won't find it. But it's already popped from the heap, so it doesn't matter.
    Wait, there's a small bug in my `get_next_job` logic: if `process_job` removes it from `self.jobs`, but it's still in the `heap`, `get_next_job` will pop it, see it's missing from `self.jobs`, and then recurse. That works.

    One detail: "FIFO order within the same priority".
    My `counter` handles this perfectly because `(-priority, counter, job_id)` will sort by `priority` first (descending because of `-`), then by `counter` (ascending).

    One more detail: `process_job` must return `bool`.
    If `RetryPolicy.run` returns `(False, attempts)`, then `process_job` returns `False`.

    *Wait*, `Job.retries` field.
    Does `RetryPolicy` update it? It doesn't receive the `Job` object.
    Does `JobQueue.process_job` update it?
     The `RetryPolicy.run` returns `attempts_made`.
     If `attempts_made > 1`, it means it retried.
     Should I update `job.retries = attempts_made - 1`?
     The prompt doesn't specify. I'll leave the `retries` field alone as it's just a dataclass field and no instruction says to update it.

    *   `Job` dataclass: `id`, `data`, `priority`, `retries`.
    *   `RetryPolicy`: `__init__`, `run(fn, data) -> (bool, int)`.
    *   `JobQueue`: `add_job`, `process_job`, `get_next_job`.
    *   No sleep.
    *   Only stdlib.
    *    Only Python code.

    Let's check `get_next_job` return type: `tuple[str, dict] | None`.
    My implementation: `return (job.id, job.data)` which is a tuple of `str` and `dict`. If empty, `None`. Correct.

    Wait, one small detail:
    If `process_job` is called and it succeeds, should it be removed from the queue?
    Usually, yes. If it's not removed, `get_next_job` would return it again?
    Wait, `get_next_job` pops from the heap.
    So even if `process_job` doesn't remove it from `self.jobs`, `get_next_job` won't return it again because it's gone from the `heap`.
    So I don't strictly *need* to delete it from `self.jobs` in `process_job`, but it's cleaner.
    However, if I *don't* delete it from `self.jobs`, then `process_job` can be called multiple times on the same `job_id`.
    If I *do* delete it, it can only be processed once.
    Most job systems process a job once. I'll delete it on success.

    Actually, let's look at the prompt again:
    "orchestrates Jobs using RetryPolicy".
    It doesn't say `process_job` removes the job.
    But `get_next_job` returns the highest priority job.
    If I call `get_next_job` twice, I should get two different jobs.
    Since `heapq.heappop` is used, I *will* get two different jobs.
    So `self.jobs` doesn't strictly need to be cleaned up by `process_job`, but I'll do it to be safe.

    Actually, one edge case:
    What if `process_job` is called *before* `get_next_job`?
    Then `self.jobs` is the only thing holding the job.
    If `process_job` removes it, `get_next_job` would return `None` or skip it.
    This is fine.

    Final check on `RetryPolicy.run`:
    "calls `fn(data)`, retries on any exception until success or `max_attempts` reached, returns `(success, attempts_made)`."
    If `max_attempts` is 1, and it fails, `attempts_made` is 1.
    If `max_attempts` is 4, and it fails 4 times, `attempts_made` is 4.
    My code:
    ```python
    attempts = 0
    while attempts < self.max_attempts:
        try:
            fn(data)
            return True, attempts + 1
        except Exception:
            attempts += 1
    return False, attempts
    ```
    Example: `max_attempts=1`.
    Loop 1: `attempts=0`. `0 < 1` is true.
    `fn` fails. `attempts` becomes 1.
    Loop 2: `1 < 1` is false.
    Returns `False, 1`. Correct.
    Example: `max_attempts=1`. `fn` succeeds.
    Loop 1: `attempts=0`. `0 < 1` is true.
    `fn` succeeds. Returns `True, 1`. Correct.

    One final detail: `Job` dataclass fields.
    `priority: int = 0`, `retries: int = 0`.
    The prompt says `retries` is a field.
    In `RetryPolicy.run`, it returns `attempts_made`.
    I'll just make sure the `Job` dataclass is exactly as requested.

    Wait, `JobQueue.process_job` signature: `process_job(self, job_id: str, processor: Callable) -> bool`.
    The `RetryPolicy.run` doesn't take `job_id`, it takes `fn` and `data`.
    So `process_job` will extract `data` from `self.jobs[job_id]`.

    ```python
    from dataclasses import dataclass
    from typing import Callable
    import heapq

    @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_made = 0
            while attempts_made < self.max_attempts:
                try:
                    fn(data)
                    return True, attempts_made + 1
                except Exception:
                    attempts_made += 1
            return False, attempts_made

    class JobQueue:
        def __init__(self):
            self.jobs = {}
            self.heap = []
            self.counter = 0
            self.retry_policy = RetryPolicy()

        def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
            job = Job(id=job_id, data=data, priority=priority)
            self.jobs[job_id] = job
            heapq.heappush(self.heap, (-priority, self.counter, job_id)))
            self.counter += 1

        def process_job(self, job_id: str, processor: Callable) -> bool:
            job = self.jobs.get(job_id)
            if not job:
                return False
            
            success, _ = self.retry_policy.run(processor, job.data)
            if success:
                del self.jobs[job_id]
                return True
            return False

        def get_next_job(self) -> tuple[str, dict] | None:
            while self.heap:
                _, _, job_id = heapq.heappop(self.heap)
                job = self.jobs.get(job_id)
                if job:
                    return (job.id, job.data)
            return None
    ```
    Wait, `get_next_job` should return `(id, data)`.
    If I pop from heap and it's not in `self.jobs` (because `process_job` deleted it), I should continue popping.
    My `while` loop handles that.
    Is there any case where `self.jobs` would have a job that's NOT in the heap?
    Yes, if it's being processed. But `get_next_job` only cares about what's in the heap.
    So this logic is sound.

    One tiny check: "WFIFO order within the same priority".
    My `counter` is incremented every time `add_job` is called.
    `heapq.heappush(self.heap, (-priority, self.counter, job_id)))`
    Higher priority (larger number) becomes more negative, thus smaller in min-heap.
    Same priority: smaller `counter` (earlier) comes first.
    This is perfect.

    Check imports: `dataclass`, `Callable`, `heapq`.
    `typing` is needed for `Callable` and `tuple` type hints?
    The prompt uses `tuple[bool, int]` and `tuple[str, dict] | None`.
    This syntax `tuple[...]` is Python 3.9+. `|` is Python 3.10+.
    I will assume a modern Python version. I'll import `Callable` from `typing`.

    Final Code structure:
    ```python
    from dataclasses import dataclass
    from typing import Callable
    import heapq

    @dataclass
    class Job:
        ...
    
    class RetryPolicy:
        ...
    
    class JobQueue:
        ...
    ```

    Wait, `JobQueue.process_job` logic:
    If `process_job` is called and it fails, should it stay in the queue?
    In my current logic, if `process_job` fails, it's still in `self.jobs` and still in `self.heap`.
    So `get_next_job` will eventually return it again.
    This is correct behavior for a queue with retries.
    If it succeeds, it's removed from `self.jobs`.
    `get_next_job` will then skip it because it's no longer in `self.jobs`.
    Actually, if `process_job` succeeds, I should probably also remove it from the heap?
    But I can't easily remove from a heap.
    However, it doesn't matter because `get_next_job` will pop it, see it's missing from `self.jobs`, and skip it.
    This is fine.

    Wait, if `process_job` is called, and it succeeds, but I *don't* remove it from `self.jobs`, and then `get_next_job` is called, it will still pop it from the heap.
    So the only way to ensure `get_next_job` doesn't return a "processed" job is to either:
    1. Remove it from `self.jobs` when processed.
    2. Remove it from the heap when processed (hard).

    I'll go with option 1.

    One last check: `retries` field in `Job`.
     Should I update it? The prompt doesn't say.
     However, it's a field. Usually, one might expect `job.retries` to track how many times it has been attempted.
     Since `RetryPolicy.run` returns `attempts_made`, I could do `job.retries = attempts_made - 1`.
     But I'll skip it to avoid over-engineering, as the prompt is silent.

    Wait, I should check if `process_job` should return `True` only on success. Yes, `-> bool`.

    One small thing: `process_job` signature: `process_job(self, job_id: str, processor: Callable) -> bool`.
    Is `processor` supposed to take `data`?
    `RetryPolicy.run(self, fn: Callable, data)` calls `fn(data)`.
    So `processor` must take `data`. Correct.

    ```python
    from dataclasses import dataclass
    from typing import Callable
    import heapq

    @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_made = 0
            while attempts_made < self.max_attempts:
                try:
                    fn(data)
                    return True, attempts_made + 1
                except Exception:
                    attempts_made += 1
            return False, attempts_made

    class JobQueue:
        def __init__(self):
            self.jobs = {}
            self.heap = []
            self.counter = 0
            self.retry_policy = RetryPolicy()

        def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
            job = Job(id=job_id, data=data, priority=priority)
            self.jobs[job_id] = job
            heapq.heappush(self.heap, (-priority, self.counter, job_id)))
            self.counter += 1

        def process_job(self, job_id: str, processor: Callable) -> bool:
            job = self.jobs.get(job_id)
            if not job:
                return False
            
            success, _ = self.retry_policy.run(processor, job.data)
            if success:
                del self.jobs[job_id]
                return True
            return False

        def get_next_job(self) -> tuple[str, dict] | None:
            while self.heap:
                _, _, job_id = heapq.heappop(self.heap)
                job = self.jobs.get(job_id)
                if job:
                    return (job.id, job.data)
            return None
    ```
    Wait, `self.jobs.get(job_id)` inside `get_next_job` will return `None` if `process_job` already succeeded.
    So the `while` loop will correctly skip it.
    This looks solid.

    Actually, one more thing:
    If `process_job` fails, the job stays in `self.jobs` and the `heap`.
    So `get_next_job` will eventually find it again. This is good.
    If `process_job` succeeds, the job is removed from `self.jobs`.
    `get_next_job` will pop it from the heap, see it's gone, and move to the next one. This is also good.

    Final check on `Job` fields: `id`, `data`, `priority`, `retries`.
    Checked.
    Final check on `RetryPolicy` fields: `max_attempts`.
    Checked.
</reasoning_content>

<content>
```python
from dataclasses import dataclass
from typing import Callable
import heapq

@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_made = 0
        while attempts_made < self.max_attempts:
            try:
                fn(data)
                return True, attempts_made + 1
            except Exception:
                attempts_made += 1
        return False, attempts_made

class JobQueue:
    def __init__(self):
        self.jobs = {}
        self.heap = []
        self.counter = 0
        self.retry_policy = RetryPolicy()

    def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
        job = Job(id=job_id, data=data, priority=priority)
        self.jobs[job_id] = job
        # Use negative priority for max-heap behavior with heapq min-heap
        # Use counter to ensure FIFO order for same priority
        heapq.heappush(self.heap, (-priority, self.counter, job_id)))
        self.counter += 1

    def process_job(self, job_id: str, processor: Callable) -> bool:
        job = self.jobs.get(job_id)
        if not job:
            return False
        
        success, _ = self.retry_policy.run(processor, job.data)
        if success:
            del self.jobs[job_id]
            return True
        return False

    def get_next_job(self) -> tuple[str, dict] | None:
        while self.heap:
            _, _, job_id = heapq.heappop(self.heap)
            job = self.jobs.get(job_id)
            if job:
                return (job.id, job.data)
        return None
```
</content>