<reasoning_content>
*   Task: Refactor a job queue into three composable classes.
    *    Files: `solution.py`.
    *    Classes required: `Job`, `RetryPolicy`, `JobQueue`.
    *    Requirements for `Job`:
        *   Dataclass.
        *   Fields: `id` (str), `data` (dict), `priority` (int, default 0), `retries` (int, default 0).
    *    Requirements for `RetryPolicy`:
        *   `__init__(max_attempts: int = 4)`.
        *   `run(fn: Callable, data) -> tuple[bool, int]`.
        *   Retry logic: call `fn(data)`, catch exceptions, retry until success or max attempts reached.
        *   Constraint: Do NOT sleep.
    *    Requirements for `JobQueue`:
        *   `add_job(job_id, data, priority=0)`.
        *   `process_job(job_id, Processor)`. Must use `RetryPolicy`.
        *   `get_next_job()`. Return `(id, data)` of highest priority (FIFO within same priority).
    *    Constraints: Standard library only. No external packages. No explanations.

    *   `Job` is simple.
    *   `RetryPolicy` needs to handle exceptions.
    *   `JobQueue` needs a way to store jobs and sort them by priority (descending) and insertion order (ascending). A list or a `heapq` could work, but since priority is a value and order matters, a sorted list or a priority queue is ideal. However, the prompt says "FIFO order within same priority".
    *   `JobQueue.process_job`: needs to find the job by ID, run it using the policy, and update the retry count? The prompt doesn't explicitly say to update the retry count on the `Job` object, but `Job` has a `retries` field. Let's check: "must USE RetryPolicy (compose, don't reimplement retry logic)".
    *   `JobQueue.get_next_job`: Needs to return highest priority.

    *   *Class Job:*
        ```python
        from dataclasses import dataclass
        from typing import Callable

        @dataclass
        class Job:
            id: str
            data: dict
            priority: int = 0
            retries: int = 0
        ```

    *   *Class RetryPolicy:*
        ```python
        class RetryPolicy:
            def __init__(self, max_attempts: int = 4):
                self.max_attempts = max_attempts

            def run(self, fn: Callable[[dict]], 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:*
        ```python
        import heapq
        from typing import List, Optional

        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 process_job(self, job_id: str, Processor: Callable[[dict]]) -> bool:
                # Find the job
                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 # Update retries? The prompt didn't strictly ask to update, but it's good practice. Actually, the prompt says "must USE RetryPolicy".
                return success

            def get_next_job(self) -> tuple[str, dict] | None:
                if not self.jobs:
                    return None
                
                # Sort by priority desc, then index asc? 
                # Actually, just find the max priority and pick first.
                # Or just sort the whole list.
                self.jobs.sort(key=lambda x: (-x.priority, self.jobs.index(x)))
                # Wait, sorting every time might be slow but simplest.
                # Let's find max priority, then find first occurrence.
                
                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
        ```

    *   Refinement on `JobQueue`:
        To maintain FIFO within priority, we can just find the max priority and return the first job with that priority.
        Wait, `get_next_job` implies removal? Usually, "get next job" in a queue means pop. But the prompt doesn't say "remove". However, usually, a "queue" removes. Let's look at the return type: `tuple[str, dict] | None`. If I don't remove it, `get_next_job` will always return the same one. 
        Actually, standard "Queue" behavior usually implies removal. But `process_job` takes a `job_id`.
        Let's assume it doesn't remove unless specified, but usually, a `get_next` from a `Queue` means it's gone.
        Wait, if I don't remove it, how do I handle `process_job`? If `process_job` is called on a job that was "gotten", it works.
        Actually, "FIFO order within the same priority" is a sorting rule.
        Let's look at `process_job(self, job_id: str, ...)`. This implies the job is already in the queue.
        
        Let's re-read: "return the highest-priority job's (id, data)".
        If I don't remove it, the test cases (if any) would be weird. But I will stick to the prompt's wording.

    *   Refining `RetryPolicy.run`:
        "calls fn(data), retries on any exception until success or max_attempts reached, returns (success, attempts_made)".
        Wait, if it succeeds on the first try, `attempts_made` should be 1? 
        If it fails 3 times and succeeds on 4th, `attempts_made` is 4.
        If it fails 4 times and never succeeds, `attempts_made` is 4.
        Yes.

    *   Refining `JobQueue`:
        Since `process_job` uses an ID, it doesn't matter if `get_next_job` removes it or not for the logic of `process_job`.
        However, if `get_next_job` doesn't remove, the queue doesn't shrink. 
        Let's assume `get_next_job` does *not* remove because the prompt doesn't say "pop" or "remove". But it's a "Queue".
        Actually, I'll implement it such that it finds the best one but doesn't remove it, to be safe with the provided method signatures. Wait, if I don't remove it, the result of `get_next_job` will always be the same. That's usually not how a queue works.
        But `process_job` takes an ID. This suggests the user might call `get_next_job` to see what's next, then call `process_job` on that ID.

    *   Refining `JobQueue.get_next_job` sorting:
        "return the highest-priority job's (id, data); FIFO order within the same priority."
        This means if we have:
        Job A: Priority 10, Added first
        Job B: Priority 10, Added second
        Job C: Priority 20, Added third
        `get_next_job` should return C.
        If C wasn't there, it would return A.

    *   Let's double check types.
        `tuple[bool, int]` -> `tuple[bool, int]`
        `tuple[str, dict] | None` -> `Optional[tuple[str, dict]]]` (since `|` syntax is 3.10+). I'll use the 3.10+ syntax if allowed, but since "standard library" is mentioned, and no version specified, I'll use `Union` or `|` depending on the most modern standard. Python 3.10+ is standard now.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Optional, Tuple, Dict, 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[[dict]], 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[[dict]]) -> 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 max priority
            max_priority = max(j.priority for j in self.jobs)
            
            # Find the first one (FIFO) with that priority
            for j in self.jobs:
                if j.priority == max_priority:
                    return (j.id, j.data)
            return None
    ```

    *    "Use only the standard library" - Yes.
    *    "Write only the Python code" - Yes.
    *    "Job - dataclass" - Yes.
    *    "RetryPolicy - run() retries on any exception" - Yes.
    *    "RetryPolicy - Do NOT actually sleep" - Yes.
    *    "JobQueue - process_job() must USE RetryPolicy (compose, don't reimplement)" - Yes.
    *    "JobQueue - get_next_job() - highest-priority... FIFO order" - Yes.
    *    "JobQueue - get_next_job() - return (id, data)" - Yes.

    Wait, `JobQueue.process_job` takes `job_id`. If I use `next(...)`, it's O(N). That's fine for a general solution unless performance is specified.
    
    Wait, "FIFO order within the same priority". My loop approach:
    ```python
    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)
    ```
    Since `self.jobs` is a list and I append to it, the order in `self.jobs` is the insertion order. By iterating through and picking the first one matching `max_priority`, I correctly satisfy FIFO.

    One edge case: `attempts_made`.
    If `max_attempts` is 4.
    Try 1: Fails
    Try 2: Fails
    Try 3: Fails
    Try 4: Fails
    Loop ends. `attempts` is 4. Return `(False, 4)`.
    Correct.
    If Try 4: Succeeds.
    Return `(True, 4)`.
    Correct.
    If Try 1: Succeeds.
    Return `(True, 1)`.
    Correct.

    Is `self.jobs.index(x)` allowed? Better avoid it to be safe on complexity. The loop logic is better.

    One detail: `process_job` signature `(self, job_id: str, Processor: Callable)`.
    Should I update the `retries` count in the `Job` object? The prompt says "dataclass with fields ... retries: int". It's highly implied that `process_job` should update it, otherwise why have the field?

    One minor check: `tuple[bool, int]` etc. This is Python 3.9+ syntax. I will use it.

    Final check on `RetryPolicy.run`:
    The prompt says `fn(data)`.
    In `process_job`, `Processor` is passed.
    So `self.retry_policy.run(Processor, job.data)`.

    Let's refine the `JobQueue.add_job` slightly to be cleaner.
    Actually, the prompt says `JobQueue` uses `RetryPolicy`.
    Should I instantiate `RetryPolicy` inside `JobQueue`?
    Yes, "orchestrates Jobs using RetryPolicy".

    Wait, `JobQueue.process_job` signature:
    `process_job(self, job_id: str, Processor: Callable) -> bool`
    The prompt doesn't say "remove the job" from the queue in `process_job`. 
    However, usually, once a job is processed (successfully or failed), it might be removed. But I will follow the prompt strictly. It doesn't say remove.

    One last check: `retries` field in `Job`. 
    If `retry_policy.run` returns `(success, attempts)`, `job.retries += attempts` is logical.

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

    @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]], 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[[dict]]) -> 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_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
    ```
    Wait, `tuple[str, dict] | None` is Python 3.10. I'll stick to it.
    Also, `list[Job]` is 3.9+. 
    
    Wait, I should check if `priority` should be higher number = higher priority?
    "highest-priority job". Usually, in priority queues, higher number = higher priority. If not, I'd need to adjust. But usually 10 > 1.
    "FIFO order within the same priority" means if two have priority 10, the one added first is returned first. My loop over `self.jobs` (which is an append-only list) handles this perfectly.

    One small thing: `retries` field in `Job` dataclass.
    Should `RetryPolicy.run` be aware of `job.retries`?
    The prompt says `RetryPolicy.run(self, fn: Callable, data)`. It does NOT take the `Job` object or the `retries` count as an argument.
    So `RetryPolicy` is independent of the `Job` instance's current `retries` count. It just runs until `max_attempts`.
    This means `process_job`'s use of `RetryPolicy` is stateless regarding the `Job`'s progress?
    Actually, if `process_job` is called multiple times on the same ID, `RetryPolicy.run` will start from 0 attempts every time? 
    Actually, `RetryPolicy` is initialized with `max_attempts=4`. 
    If `process_job` is called, it runs up to 4 times. 
    This seems to be the requirement.

    Wait, I notice `RetryPolicy.run(self, fn: Callable, data)`. 
    In the prompt description: `run(self, fn: Callable, data) -> tuple[bool, int]`.
    It doesn't specify the type of `data`. But `Job.data` is a `dict`.

    ```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[[dict]], 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[[dict]]) -> 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_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
    ```
    
    Actually, `tuple[bool, int]` and `tuple[str, dict] | None` are Python 3.10+.
    If the environment is older, `Union` and `Tuple` are safer. But the prompt asks for `tuple[bool, int]` style which is 3.10+. So I will provide that.

    One minor fix: `JobQueue.process_job` `Processor` is a `Callable`. 
    The logic `fn(data)` inside `RetryPolicy` works.

    Double check `Job` fields: `id`, `data`, `priority`, `retries`. Correct.
    Double check `RetryPolicy` fields/methods: `max_attempts`, `run`. Correct.
    Double check `JobQueue` fields/methods: `add_job`, `process_job`, `get_next_job`. Correct.

    Wait, `RetryPolicy.run` parameters are `fn` and `data`. 
    `self.retry_policy.run(Processor, job.data)` fits.

    One thing: "Do NOT actually sleep". 
    My code doesn't sleep.

    One thing: "composition".
    `JobQueue` has a `RetryPolicy` instance and calls its `run` method. This is composition.

    One detail on `get_next_job`: "highest-priority... FIFO order".
    If input is:
    [Priority 5 (Id 1)], [Priority 10 (Id 2)], [Priority 10 (Id 3)]
    My code:
    `max_priority` = 10.
    Iterates list:
    Index 0: Priority 5 != 10.
    Index 1: Priority 10 == 10. Returns Id 2.
    Correct. FIFO for the same priority (10) is satisfied because Id 2 was added before Id 3.

    One last check on `JobQueue.process_job`:
    "must USE RetryPolicy (compose, don't reimplement retry logic)".
    I am calling `self.retry_policy.run(Processor, job.data)`. That is using it.

    The prompt asks for `solution.py`. I will provide the content of that file.

    ```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[[dict]], 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[[dict]]) -> 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_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
    ```
    One small check: `max_attempts` is 4.
    If it fails 4 times, `attempts` is 4, loop ends, returns `(False, 4)`.
    Wait, if it succeeds on the 4th try:
    `attempts` starts 0.
    Try 1: Fail (attempts=1)
    Try 2: Fail (attempts=2)
    Try 3: Fail (attempts=3)
    Try 4: Succeed. `attempts` is 3. `attempts + 1` is 4. Returns `(True, 4)`.
    Wait, if `attempts` starts at 0.
    Loop 1: `attempts` is 0. `attempts < 4` is True.
    Try 1: Fails. `attempts` becomes 1.
    Loop 2: `attempts` is 1. `attempts < 4` is True.
    Try 2: Fails. `attempts` becomes 2.
    Loop 3: `attempts` is 2. `attempts < 4` is True.
    Try 3: Fails. `attempts` becomes 3.
    Loop 4: `attempts` is 3. `attempts < 4` is True.
    Try 4: Fails. `attempts` becomes 4.
    Loop 5: `attempts` is 4. `attempts < 4` is False.
    Returns `(False, 4)`. Correct.

    If Try 4 succeeds:
    Loop 4: `attempts` is 3. `attempts < 4` is True.
    Try 4: Succeeds.
    Returns `(True, 3 + 1)` which is `(True, 4)`. Correct.

    What if it succeeds on Try 1?
    Loop 1: `attempts` is 0. `attempts < 4` is True.
    Try 1: Succeeds.
    Returns `(True, 0 + 1)` which is `(True, 1)`. Correct.

    The logic is sound.

    One more check: `RetryPolicy.run` parameters: `fn: Callable, data`.
    In the prompt `run(self, fn: Callable, data)`.
    I should ensure I don't add a type hint to `data` if not strictly necessary, but `dict` is safe.

    One more thing: `tuple[str, dict] | None`.
    The `|` operator for Union is Python 3.10. Since the prompt uses `tuple[bool, int]`, it's safe to assume 3.10+ syntax is expected.

    Final look at the code:
    ```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[[dict]], 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[[dict]]) -> 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_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
    ```
    Wait, I should use `list[Job]` or just `list`? `list[Job]` is safer for 3.9+. 
    Actually, the prompt doesn't specify versions. I'll use simple types for readability if unsure, but `tuple[...]` implies 3.9+.
    I'll stick to the provided style.

    One last check: "retries: int = 0".
    My `Job` class: `retries: int = 0`. Correct.
    `JobQueue.add_job` signature: `(self, job_id: str, data: dict, priority: int = 0)`. Correct.
    `JobQueue.process_job` signature: `(self, job_id: str, Processor: Callable)`. Correct.
    `JobQueue.get_next_job` signature: `(self) -> tuple[str, dict] | None`. Correct.

    One tiny thing: `RetryPolicy.run(self, fn: Callable, data)`.
    My code: `def run(self, fn: Callable[[dict]], data: dict) -> tuple[bool, int]:`.
    Wait, if I use `Callable[[dict]]`, it might be too specific. `Callable` is safer.
    I will use `Callable`.

    Wait, "Do NOT actually sleep". 
    Check.

    Final check on `JobQueue.process_job`:
    Should I return `True` only if `success` is `True`? 
    Yes, `return success`.
    If `job` is not found, return `False`. Correct.

    ```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_priority = max(j.priority for j in self.jobs)
            for j in self.jobs:
                if j.priority == max_priority:
                    return (j.id, jj.data)
    ```
    Wait, `jj.data` was a typo. Fixed. `j.data`.

    One final check on `get_next_job` return type:
     `tuple[str, dict] | None`
    If `self.jobs` is empty, I return `None`.
    If not empty, I return `(j.id, j.data)`.
    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_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
```
</content>