The jobs have start times, end times, and nonnegative rewards. I need the largest total reward of a compatible subset, where a job ending exactly when another starts is compatible. This is weighted interval scheduling, so choosing the earliest-finishing job greedily is insufficient. A single long job can be worth more than several short jobs, and several modest jobs can together beat a valuable-looking overlap. The decision must compare total rewards of prefixes.

I will sort jobs by end time. For a job at position i, every earlier compatible job lies among those whose end is at most the current start. If I know the best reward attainable from each sorted prefix, I can either skip the current job or take it after the best compatible prefix. The maximum of those two choices gives the new prefix optimum. This is the main recurrence; the remaining question is how to locate the compatible prefix correctly.

The straightforward draft scans backwards for it. That makes the recurrence visible before introducing a binary search.

```python
def maximum_reward(jobs):
    ordered = sorted(jobs, key=lambda job: job[1])
    best = [0] * (len(ordered) + 1)
    for i, (start, end, reward) in enumerate(ordered):
        compatible = i - 1
        while compatible >= 0 and ordered[compatible][1] > start:
            compatible -= 1
        take = reward + best[compatible + 1]
        best[i + 1] = max(best[i], take)
    return best[-1]
```

This version handles the recurrence, but its backward scan makes the worst-case running time quadratic. If most jobs overlap, every iteration traverses almost the entire earlier prefix without finding a compatible endpoint. The sorted list of end times already has the order needed for a binary search, so I should use that structure instead of repeating a linear scan.

But wait, there is an indexing trap in the replacement. A binary search over all end times can include the current job when jobs with zero duration are allowed. For a job with start equal to end, its own end is at most its start. If the search returns a prefix including that job, the recurrence reads a state that is supposed to be computed by the current iteration. In some tie configurations it could even include later jobs. The search must therefore be restricted to the earlier i jobs.

Using bisect_right(ends, start, 0, i) produces the number of earlier jobs whose end is at most start. That number is exactly the index into best, because best[k] describes the first k jobs. There is no need to subtract one and then add one again. The right-bound choice also preserves the compatibility rule at equal endpoints: bisect_left would exclude jobs ending precisely at the current start and solve a stricter problem.

I should make the assumptions about job duration explicit. If zero-duration jobs are supported, ordering equal-end jobs by end alone can still matter. A zero-duration job at time t can be combined with another job ending at t, but placing that zero-duration job first may prevent the later ordinary job from using it even though the reverse chronological interpretation is compatible. Sorting by (end, start) places ordinary jobs ending at t before zero-duration jobs at t. Multiple zero-duration jobs can then accumulate in sequence because each sees all earlier jobs in its bounded prefix.

That observation changes the sort key in the final form. It is a useful check on the phrase "sort by end time": the broad recurrence is right, but a tie policy can be necessary when the input includes degenerate intervals. If the problem instead guaranteed start < end, the simpler end-only key would be enough. I will support start <= end here and reject reversed intervals, so the stronger key is appropriate.

Negative rewards do not break the recurrence either, since skipping every job still yields zero. The problem says nonnegative rewards, but there is no need to reject a negative value merely to keep the algorithm valid. I will preserve it as given and let the maximum choose whether to use the job. For endpoints, reversed order is structurally invalid because it contradicts the interval interpretation; that is worth a ValueError. The input itself will be copied before sorting so its original ordering is preserved.

For a concrete example, consider (0, 3, 5), (2, 5, 6), and (3, 6, 7). The second job cannot follow the first, so the best reward after two jobs is six. The third job can follow the first because the first ends at its start. Taking it gives twelve, better than skipping it. A greedy choice based on the second job's reward would not expose that combination. The prefix table retains enough information to recover it.

The empty input produces best = [0], so the answer is zero. A list of mutually overlapping positive-duration jobs produces the largest individual reward. A chain of touching jobs accumulates every reward. Several copies of a zero-duration job at the same point can all be selected under the stated compatibility definition; the sorted order and earlier-prefix search allow exactly that result. These cases test the meaning of compatibility as well as ordinary indexing.

The recurrence proof is by induction on the prefix length. An optimal subset of the first i + 1 jobs either excludes job i, in which case its reward is at most best[i], or includes it. If included, every other selected job must be in the compatible earlier prefix. By the induction hypothesis their total reward is at most best[compatible_count]. Conversely, appending job i to an optimal compatible prefix constructs a feasible subset of exactly that candidate reward. Taking the maximum is therefore both an upper bound and attainable.

Here is the final function with the bounded binary search and the explicit tie ordering:

Sorting takes O(n log n), and each of n bounded searches takes O(log n). The prefix table and end-time list use O(n) space. Reconstructing the chosen jobs would require storing decisions or walking the table afterwards, but the requested output is only the reward. The final code returns that quantity directly.

```python
from bisect import bisect_right


def maximum_reward(jobs):
    """Return the best reward when touching endpoints are compatible."""
    ordered = list(jobs)
    if any(start > end for start, end, reward in ordered):
        raise ValueError("a job starts after it ends")
    ordered.sort(key=lambda job: (job[1], job[0]))
    ends = [end for start, end, reward in ordered]
    best = [0] * (len(ordered) + 1)
    for i, (start, end, reward) in enumerate(ordered):
        compatible_count = bisect_right(ends, start, 0, i)
        take = reward + best[compatible_count]
        best[i + 1] = max(best[i], take)
    return best[-1]
```
