<reasoning_content>
*   Goal: Refactor a job queue into three composable classes: `Job`, `RetryPolicy`, and `JobQueue`.
    *   Language: Python (standard library only).
    *   Constraints:
        *   `Job`: Dataclass (`id`, `data`, `priority`, `retries`).
        *   `RetryPolicy`: `max_attempts` (default 4). `run(fn, data)` returns `(success, attempts_made)`. No actual sleeping.
        *   `JobQueue`: `add_job`, `process_job` (must use `RetryPolicy`), `get_next_job` (highest priority, FIFO within priority).
    *   Output: Python code only, 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[[dict], any], 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
        ```

    *   `JobQueue`:
        ```python
        import heapq
        from typing import List, Tuple, Optional

        class JobQueue:
            def __init__(self):
                self.jobs: List[tuple[int, int, str, dict]]] = [] # Heap stores (-priority, sequence_id, id, data)
                self.counter = 0 # To maintain FIFO for same priority
                self.retry_policy = RetryPolicy()

            def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
                # Heapq is a min-heap. To get max-priority, we use -priority.
                # We need a counter to ensure FIFO for same priority.
                heapq.heappush(self.jobs, (-priority, self.counter, job_id, data))
                self.counter += 1

            def process_job(self, job_id: str, processor: Callable[[dict], any]) -> bool:
                # The requirement says "must USE RetryPolicy".
                # However, process_job signature takes job_id.
                # This implies searching for the job?
                # Wait, usually JobQueue would have a way to find a specific job or process what's next.
                # Let's look at the signatures provided.
                # process_job(self, job_id: str, processor: Callable) -> bool
                # This signature suggests finding the job with that ID.
                for i, (neg_pri, count, j_id, data) in enumerate(self.jobs):
                    if j_id == job_id:
                        success, _ = self.retry_policy.run(processor, data)
                        # If failed, should we re-add or leave?
                        # The prompt doesn't specify handling on failure in process_job,
                        # just that it must use RetryPolicy and return bool.
                        return success
                return False

            def get_next_job(self) -> tuple[str, dict] | None:
                if not self.jobs:
                    return None
                neg_pri, count, j_id, data = heapq.heappop(self.jobs)
                return (j_id, data)
        ```

    *   `Job` dataclass? Yes.
    *   `RetryPolicy` constructor? Yes.
    *   `RetryPolicy.run` signature? Yes.
    *   `RetryPolicy.run` no sleep? Yes.
    *   `JobQueue` methods? Yes.
    *   `JobQueue.process_job` uses `RetryPolicy`? Yes.
    *   `JobQueue.get_next_job` priority and FIFO? Yes.
    *   Standard library only? Yes.
    *   No explanations? Yes.

    *Refining `JobQueue`*:
    Wait, `process_job` asks for `job_id`. If I pop the job to process it, and it fails, it might be lost. But the signature doesn't suggest popping. If I search and process, and it fails, `success` is returned.
    However, standard job queues usually pull a job, then process it.
    But the prompt explicitly says `process_job(self, job_id: str, ...)`. This implies searching or specific selection.

    Wait, if `process_job` is called, should the job be removed from the queue?
    Usually, if `process_job` is called with an ID, it means "do this specific job". If it's successful, it's done. If not, maybe put it back?
    Actually, the prompt says "orchestrates Jobs".
    Let's look at the `Job` dataclass again. It has `retries` and `priority`.
    If I use `heapq`, I need to store the `Job` object or parts of it.

    Let's re-evaluate `JobQueue.process_job`.
    If I have to use `RetryPolicy`, and `RetryPolicy.run` returns success/attempts.
    If `process_job` is called, I find the job. If it succeeds, I remove it? Or is it already removed?
    Usually, `get_next_job` removes it. `process_job` might be an alternative way to run a specific one.
    Wait, if `get_next_job` removes it, then `process_job` shouldn't be able to find it unless it's still in the queue.
    Let's assume `process_job` identifies a job currently in the queue, attempts it, and if successful, removes it. Or simply executes it.
    Actually, the cleanest way to satisfy "orchestrates" and the provided signatures:
    `add_job` adds to a list/heap.
    `get_next_job` pops the highest priority.
    `process_job` finds the job by ID, runs it via `RetryPolicy`. If success, remove it.

    Wait, let's look at `Job` dataclass again. It has `retries`.
    If `RetryPolicy` fails, should `retries` be incremented?
    The instructions don't say to increment `retries` in `RetryPolicy` or `JobQueue`. They just say `RetryPolicy` handles retries via `max_attempts`.

    Let's refine `JobQueue.process_job`.
    If `process_job` is called:
    1. Find the job with `job_id`.
    2. Run it via `retry_policy.run(processor, job.data)`.
    3. If success, remove from queue.
    4. Return success.

    Wait, `get_next_job` returns `tuple[str, dict]`. It doesn't return the `Job` object, just ID and data. This suggests that `JobQueue` internally manages the `Job` objects.

    Let's use a dictionary to store jobs by ID for easy lookup in `process_job` and a heap for `get_next_job`.

    Actually, if I use a heap, searching for an ID is $O(N)$.
    If I use a dict and a heap, it's $O(1)$ or $O(\log N)$.

    One detail: `get_next_job` returns `tuple[str, dict] | None`.
    The priority/FIFO logic:
    Priority 10 is higher than Priority 0? Usually yes.
    FIFO within same priority?
    I'll use `(-priority, sequence_number, job_id)`.

    Wait, `RetryPolicy.run` signature: `run(self, fn: Callable, data)`.
    I need to make sure `Callable` is imported.

    ```python
    from dataclasses import dataclass
    from typing import Callable, Tuple, Dict, List, Optional
    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[[dict], any], 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: Dict[str, Job] = {}
            self.heap: List[Tuple[int, int, str]] = []
            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[[dict], any]) -> bool:
            if job_id not in self.jobs:
                return False
            
            job = self.jobs[job_id]
            success, _ = self.retry_policy.run(processor, job.data)
            
            if success:
                # If successful, we should probably remove it? 
                # The prompt doesn't explicitly say to remove it here, 
                # but usually processing implies completion.
                # However, if I remove it here, get_next_job might see it.
                # Let's check the instructions. 
                # "orchestrates Jobs using RetryPolicy"
                # If I don't remove it, it stays in the heap.
                pass
            return success

        def get_next_job(self) -> Tuple[str, dict] | None:
            while self.heap:
                neg_pri, count, j_id = heapq.heappop(self.heap)
                if j_id in self.jobs:
                    job = self.jobs[j_id]
                    return (job.id, job.data)
            return None
    ```

    One issue: `process_job` doesn't specify if the job should be removed.
    If I don't remove it, and `get_next_job` is called, it might return a job that was already "processed".
    Usually, a "process" method in a queue implies the job is handled.
    If `process_job` is successful, I should probably remove it from the tracking dict and it will be skipped by `get_next_job`.

    Let's look at the prompt again.
    `process_job(self, job_id: str, processor: Callable) -> bool`
    If I don't remove it, the user can call `process_job` multiple times?
    Actually, `get_next_job` returns `tuple[str, dict]`.
    Maybe the `JobQueue` is meant to be used such that `get_next_job` is the primary way to consume.
    But `process_job` is provided as a separate method.

    Let's refine `process_job` to remove the job only if successful.
    Wait, if I remove it from `self.jobs` but not the heap, `get_next_job` will skip it because of the `if j_id in self.jobs` check. That's perfect.

    Wait, `Job` dataclass has a `retries` field.
    The `RetryPolicy` doesn't use the `retries` field from the `Job` object (it has its own `max_attempts`).
    The prompt says `RetryPolicy` encapsulates retry-with-backoff (though it says "do NOT actually sleep").
    It doesn't say `RetryPolicy` should update `job.retries`. It just says `run` returns `(success, attempts_made)`.

    One more thing: `RetryPolicy.run` signature is `run(self, fn: Callable, data)`.
    The prompt says `fn(data)`. My implementation does that.

    Refining `JobQueue.process_job`:
    The prompt doesn't specify removing the job. But a "processed" job shouldn't be "next".
    Let's keep it simple. `process_job` tries to run it. If success, it's removed.

    Wait, looking at the requirements again:
    "orchestrates Jobs using RetryPolicy"
    "process_job ... must USE RetryPolicy"
    
    If I call `process_job`, and it succeeds, and I *don't* remove it, `get_next_job` will still return it. That seems wrong for a Queue.
    If I call `process_job`, and it fails, and I *don't* remove it, `get_next_job` will still return it. That seems correct (retryable).
    So, if `process_job` returns `True`, the job is done.

    ```python
    def process_job(self, job_id: str, processor: Callable[[dict], any]) -> 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 success
    ```

    Actually, the `Job` dataclass has `retries`. Should I increment `job.retries` inside `RetryPolicy`? The prompt doesn't say so. It says `RetryPolicy` encapsulates retry logic and returns `attempts_made`. It doesn't say it interacts with the `Job` object.

    Wait, the `Job` dataclass `retries` field... is it used? 
    The prompt doesn't say to use it. It just says define the class with those fields.

    One final check on `get_next_job`:
    "return the highest-priority job's (id, data); FIFO order within the same priority."
    My heap logic: `(-priority, self.counter, job_id)`.
    - Higher priority (e.g., 10) becomes -10.
    - Lower priority (e.g., 0) becomes 0.
    - -10 is smaller than 0, so `heappop` gets -10 first. Correct.
    - `self.counter` ensures that for same `-priority`, the lower counter (earlier job) comes first. Correct.

    Double check `RetryPolicy.run` return type: `tuple[bool, int]`.
    `success, attempts_made = self.retry_policy.run(...)`.

    Final code structure:
    ```python
    from dataclasses import dataclass
    from typing import Callable, Tuple, Dict, List, Optional
    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[[dict], any], 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: Dict[str, Job] = {}
            self.heap: List[Tuple[int, int, str]] = []
            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[[dict], any]) -> 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 success

        def get_next_job(self) -> Tuple[str, dict] | None:
            while self.heap:
                neg_pri, count, j_id = heapq.heappop(self.heap)
                if j_id in self.jobs:
                    job = self.jobs[j_id]
                    return (job.id, job.data)
            return None
    ```

    Wait, `Tuple` vs `tuple`? The prompt uses `tuple[...]` in the description but `Callable` usually implies typing. I'll use `Tuple` and `Dict` from `typing` for compatibility, or just use native `tuple` if I'm on Python 3.9+. The prompt uses `tuple[...]` notation which is 3.9+. I'll stick to standard types where possible.

    Actually, `from typing import Callable, Tuple, Dict, List, Optional` is safer for "standard library".

    Wait, `retry-with-backoff` is mentioned in the `RetryPolicy` description, but then it says `Do NOT actually sleep`. This means I don't need to implement the actual backoff math (like `2**attempts`), just the loop logic.

    Final check on `JobQueue.process_job` requirement:
    `process_job(self, job_id: str, processor: Callable) -> bool`
    I must use `RetryPolicy`.
    My implementation does: `self.retry_policy.run(processor, job.data)`.

    One small detail: `Job` dataclass `retries` field.
    If the `RetryPolicy` is meant to be used to *update* the `Job` object, I should do that. But `RetryPolicy.run` only returns `(success, attempts_made)`. It doesn't receive the `Job` object, only the `data`. So it can't update `job.retries`.
    I will leave `retries` as is, as the instructions didn't say to update it.

    Wait, one more look at `RetryPolicy.run(self, fn: Callable, data)`. 
    It doesn't take the `Job` object. So it's impossible to update `job.retries` inside `run`.

    Actually, `JobQueue` could increment `job.retries` in `process_job` if `run` returns `False`.
    But the `RetryPolicy` is supposed to "encapsulate" it.
    If `RetryPolicy` is only given `data` and `fn`, it can't know about the `Job` object's `retries` field.
    So I'll just stick to the requirements strictly.

    One thing: `from typing import Callable`.
    `fn: Callable` usually takes a type hint like `Callable[[dict], any]`.
    I'll just use `Callable` as requested.

    Final Code Construction.
</reasoning_content>

<content>
```python
from dataclasses import dataclass
from typing import Callable, Tuple, Dict, List, Optional
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: Dict[str, Job] = {}
        self.heap: List[Tuple[int, int, str]] = []
        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 using heapq min-heap
        # Use counter to ensure FIFO 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_or_id := job_id]
        return success

    def get_next_job(self) -> Tuple[str, dict] | None:
        while self.heap:
            neg_pri, count, j_id = heapq.heappop(self.heap)
            if j_id in self.jobs:
                job = self.jobs[j_id]
                return (job.id, job.data)
        return None
```
</content>