<reasoning_content>
*   Goal: Refactor a job queue into three composable classes: `Job`, `RetryPolicy`, and `JobQueue`.
    *   Constraints:
        *   Use `dataclass` for `Job`.
        *   `RetryPolicy` handles retries with backoff (but no actual sleep).
        *   `JobQueue` orchestrates jobs using `RetryPolicy`.
        *   `JobQueue` methods: `add_job`, `process_job`, `get_next_job`.
        *   `get_next_job` logic: Highest priority first, then FIFO for same priority.
        *   No external packages (standard library only).
        *   Output: Only Python code, no explanations.

    *   `Job`:
        ```python
        from dataclasses import dataclass
        from typing import Dict, Callable, Tuple, Optional

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

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

            def run(self, fn: Callable, data: dict) -> Tuple[bool, int]:
                attempts_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
        ```
        *Correction on `RetryPolicy` logic*: The prompt says "retries on any exception until success or max_attempts reached". Usually, "attempts made" means the count of tries. If it fails all attempts, it returns `(False, max_attempts)`. If it succeeds on the first try, `(True, 1)`.

    *   `JobQueue`:
        ```python
        import heapq

        class JobQueue:
            def __init__(self):
                self.jobs = [] # Use a heap or a list?
                # Requirements: Highest priority first, then FIFO.
                # Python's heapq is a min-heap.
                # To make it a max-heap for priority, we use negative priority.
                # To maintain FIFO, we need a sequence counter.
                self.counter = 0
                self.retry_policy = RetryPolicy()

            def add_job(self, job_id: str, data: dict, priority: int = 0) -> None:
                # Need to store Job object.
                # priority is max-heap style (higher is better).
                # Use -priority for heapq.
                job = Job(id=job_id, data=data, priority=priority)
                heapq.heappush(self.jobs, (-priority, self.counter, job))
                self.counter += 1

            def process_job(self, job_id: str, processor: Callable) -> bool:
                # Search for the job? Or does it take the next one?
                # Method signature: process_job(self, job_id: str, processor: Callable) -> bool
                # This implies looking up a specific ID.
                # Wait, if I look up an ID, I might need to remove it from the heap.
                # Or maybe I process a specific job?
                # Looking at the prompt: "orchestrates Jobs using RetryPolicy".
                # The method signature is process_job(self, job_id: str, ...)
                # Let's check if jobs are stored in a dict for easy lookup or a list.
                # If I use a heap, lookup by ID is O(N).
                pass

            def get_next_job(self) -> Tuple[str, dict] | None:
                # return highest-priority job's (id, data); FIFO order within same priority.
                if not self.jobs:
                    return None
                _, _, job = heapq.heappop(self.jobs)
                return job.id, job.data
        ```

    *   *Refining `JobQueue` storage:*
        Since `process_job` takes a `job_id`, I should probably store jobs in a dictionary for O(1) lookup, but `get_next_job` requires priority ordering.
        Actually, usually in these types of designs, `process_job` might be expected to find the job in the queue.
        Wait, `get_next_job` returns the id and data. `process_job` takes an id.
        If `process_job` is called with an ID, I need to find that job.
        Let's use a dictionary for storage and a list for the heap (or just a dictionary if order isn't strictly required for the dictionary itself).

    *   *Refining `process_job` requirements:*
        "must USE RetryPolicy (compose, don't reimplement retry logic)".
        The `RetryPolicy.run` returns `(success, attempts_made)`.
        The `Job` object has a `retries` field? Or is that for the `RetryPolicy`?
        The `Job` dataclass has `retries: int = 0`.
        Maybe `process_job` updates the `retries` on the `Job` object?

    *   *Implementation Detail - `JobQueue` structure:*
        To support `get_next_job` (Priority + FIFO):
        Priority Queue (min-heap) storing `(-priority, sequence_number, job_object)`.
        To support `process_job(job_id)`:
        A dictionary `self.all_jobs = {job_id: job_object}`.
        Wait, if a job is processed, is it removed? The prompt doesn't specify. Usually, processing completes a job.

    *   *Wait, the `process_job` signature*:
        `process_job(self, job_id: str, processor: Callable) -> bool`
        If I find the job in the dictionary, use `retry_policy.run(processor, job.data)`, then what?
        The prompt doesn't say whether to remove it or how to handle the `retries` field.
        I will assume `process_job` finds the job, applies the policy, and returns the boolean result.

    *   *Handling "Highest priority":*
        Higher priority value = higher priority? Usually yes.
        Python's `heapq` is a min-heap. So I use `-priority`.

    *   *Sequence Counter:*
        To ensure FIFO for same priority, `heapq` needs a secondary sort key that increments.

    *   *RetryPolicy logic check*:
        "retries on any exception until success or max_attempts reached".
        `run` takes `fn` and `data`.
        If it succeeds, return `(True, attempts_made)`.
        If it fails all attempts, return `(False, max_attempts)`.

    *   *Wait, `Job.retries`*:
        Should `process_job` update the `retries` count on the `Job` object?
    *   *Actually, let's look at the instructions again.*
        "orchestrates Jobs using RetryPolicy".
        If `process_job` is called, it should find the job, run it through the policy.

    *   *Refining the storage architecture*:
        `self.jobs = {}` (id -> Job)
        `self.priority_queue = []` (heap)

        When `add_job` is called:
        1. Create `Job` object.
        2. Store in `self.jobs`.
        3. Push to `self.priority_queue`.

        When `get_next_job` is called:
        1. Pop from `self.priority_queue`.
        2. Return `(id, data)`.

        When `process_job(job_id, processor)` is called:
        1. Get `Job` object from `self.jobs`.
        2. Call `retry_policy.run(processor, job.data)`.
        3. (Optional but likely) Update `job.retries`.
        4. Return success boolean.

    *   *One edge case*: If `get_next_job` pops it, but `process_job` is called with an ID? If it's already popped, it's not in the queue but still in the "system".
    *   Actually, `get_next_job` returns `(id, data)`. Usually, "getting" a job removes it from the queue (otherwise it would be returned repeatedly).
    *   If `get_next_job` removes it from the priority queue, `process_job` still needs to find it. So I should keep a reference to all jobs processed/active? Or maybe `process_job` is meant to be used on a job that was "gotten"?
    *   But the signature is `process_job(self, job_id: str, ...)`. This implies the ID is provided.
    *   Let's stick to:
        `add_job`: adds to a dict and a heap.
        `get_next_job`: pops from the heap (removes from heap, but keep in dict for `process_job`).
        `process_job`: looks up in dict, uses policy.

    Wait, the requirement for `get_next_job` is "return the highest-priority job's (id, data)". If I don't remove it, the same job will be returned forever. Standard queue behavior is removal.

    Let's refine `JobQueue`:
    - `self.jobs = {}` (ID -> Job)
    - `self.pq = []` (Heap)
    - `self.counter = 0`

    `add_job(id, data, priority)`:
    - `job = Job(id, data, priority)`
    - `self.jobs[id] = job`
    - `heapq.heappush(self.pq, (-priority, self.counter, job))`
    - `self.counter += 1`

    `get_next_job()`:
    - `if not self.pq: return None`
    - `_, _, job = heapq.heappop(self.pq)`
    - `return job.id, job.data`

    `process_job(job_id, processor)`:
    - `job = self.jobs.get(job_id)`
    - `if not job: return False`
    - `success, attempts = self.retry_policy.run(processor, job.data)`
    - `job.retries = attempts` (or similar logic)
    - `return success`

    *Self-correction on `RetryPolicy.run`*:
    "retries on any exception until success or max_attempts reached, returns (success, attempts_made)".
    The number of attempts made should be the total count of calls to `fn`.
    If it succeeds on the 2nd try, `attempts_made` is 2.
    If it fails all 4 tries, `attempts_made` is 4.

    ```python
    def run(self, fn: Callable, data: dict) -> tuple[bool, int]:
        attempts_made = 0
        while attempts_made < self.max_attempts:
            attempts_made += 1
            try:
                fn(data)
                return True, attempts_made
            except Exception:
                continue
        return False, attempts_made
    ```

    *Wait, check the order requirement*: "Highest-priority job's (id, data); FIFO order within the same priority."
    Heap logic: `(-priority, counter, job_id)`.
    By using `counter` as the second element, if priorities are equal, the one with the smaller `counter` (added earlier) will be popped first. This satisfies FIFO.

    *Double check `RetryPolicy` definition*:
    `run(self, fn: Callable, data) -> tuple[bool, int]`
    Prompt says `data` without type hint in the signature but `data: dict` in `add_job`. I'll stick to the signature provided but use `dict` internally. Actually, the prompt says `run(self, fn: Callable, data)`. I will omit the type hint for `data` in the signature to match exactly.

    *Double check `Job` fields*:
    `id: str`, `data: dict`, `priority: int = 0`, `retries: int = 0`.

    *Final check on `JobQueue` methods*:
    1. `add_job(self, job_id: str, data: dict, priority: int = 0) -> None`
    2. `process_job(self, job_id: str, processor: Callable) -> bool`
    3. `get_next_job(self) -> tuple[str, dict] | None`

    One small detail: `tuple[str, dict] | None` syntax requires Python 3.10+. Since no version was specified, I'll use `Optional[Tuple[str, dict]]` or just standard Union if needed, but `|` is cleaner. Given "standard library", I should probably use `Optional` or `Union` for compatibility if I want to be safe, but `|` is standard now. Actually, let's use `Union` or just the pipe if I assume modern Python. I'll use `Union` or `Optional` to be safe, but the prompt says `tuple[...] | None` so I will mirror that exactly.

    *Code structure*:
    ```python
    from dataclasses import dataclass
    from typing import Callable, Tuple, Optional, Dict
    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:
                attempts_made += 1
                try:
                    fn(data)
                    return True, attempts_made
                except Exception:
                    pass
            return False, attempts_made

    class JobQueue:
        def __init__(self):
            self.jobs: Dict[str, Job] = {}
            self.queue = []
            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.queue, (-priority, self.counter, job))
            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, 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.queue:
                return None
            _, _, job = heapq.heappop(self.queue)
            return job.id, job.data
    ```

    Wait, I should check if `data` in `run` needs a type hint.
    Prompt: `run(self, fn: Callable, data) -> tuple[bool, int]`
    It does *not* have a type hint for `data` in the instruction. I will follow it exactly.

    One more check on `get_next_job`:
    "return the highest-priority job's (id, data); FIFO order within the same priority."
    If I use `heapq`, and priority is high number (e.g. 10), and `-priority` is `-10`.
    If priority is low number (e.g. 1), and `-priority` is `-1`.
    `-10` is smaller than `-1`.
    So `heapq` will pop `-10` first.
    Yes, `-priority` is correct for a max-priority heap.

    Wait, `Job` class definition:
    `priority: int = 0`, `retries: int = 0`.
    `JobQueue.add_job` takes `priority: int = 0`.

    Wait, "Do NOT actually sleep" in `RetryPolicy`. Done.
</reasoning_content>

<content>
```python
from dataclasses import dataclass
from typing import Callable, Tuple, Dict
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:
            attempts_made += 1
            try:
                fn(data)
                return True, attempts_made
            except Exception:
                continue
        return False, attempts_made

class JobQueue:
    def __init__(self):
        self.jobs: Dict[str, Job] = {}
        self.queue = []
        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 and counter for FIFO
        heapq.heappush(self.queue, (-priority, self.counter, job))
        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, 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.queue:
            return None
        _, _, job = heapq.heappop(self.queue)
        return job.id, job.data
```
</content>