I need the insertion position immediately after every occurrence of the target in a sorted list. Equivalently, the result is the index of the first element strictly greater than target, or the length of the list if there is no such element. This is an upper-bound search. A linear scan would be easy, but the requested logarithmic running time suggests maintaining a shrinking interval of possible boundary positions.

Let me start with the familiar inclusive binary search, since it makes the comparison with an ordinary lookup obvious. If the middle element is at most target, the answer lies to its right. If it is greater, the answer might be that middle element or something to its left. The comparison has to include equality in the first branch, because stopping on an equal element would leave the insertion position somewhere inside a group of duplicates.

My first draft is this function:

```python
def insertion_after_target(values, target):
    low = 0
    high = len(values) - 1
    answer = len(values)
    while low <= high:
        middle = (low + high) // 2
        if values[middle] <= target:
            low = middle + 1
        else:
            answer = middle
            high = middle - 1
    return answer
```

This draft is correct, but it carries an answer variable in addition to the search interval. I want to check whether that extra state is necessary before choosing the final form. The upper-bound boundary is a position between elements, so it can range from zero through len(values), inclusive. An interval of candidate positions naturally includes the end position, while an inclusive interval of element indices needs the extra answer sentinel to represent it.

Actually, the half-open version expresses the problem more directly. I can keep low and high as boundary candidates with high initialized to len(values). Every index below low is already known to hold a value at most target. Every index at or above high is known to hold a value greater than target. The undecided elements occupy [low, high). When that interval becomes empty, both endpoints describe the one boundary consistent with the known facts.

I should distinguish the high sentinel from a valid array index. The midpoint is evaluated only while low < high, and middle is strictly less than high because of floor division. Since high never exceeds len(values), middle is always a valid element index. The code therefore never reads values[len(values)], even though the final answer may be len(values). This is the small detail that often gets lost when one describes binary search as simply halving a range.

Consider an empty input. Both low and high start at zero, the loop is skipped, and zero is returned. For a list whose elements are all at most target, each comparison moves low to the right until it equals len(values). For a list whose first element already exceeds target, the comparisons move high left until zero remains. Those are legitimate insertion positions, so there is no reason to special-case them outside the invariant.

Duplicates are the important middle case. With values [1, 4, 4, 4, 9] and target 4, a midpoint equal to 4 moves low beyond that occurrence instead of returning immediately. The invariant says all elements before the new low are at most 4, which remains true. Later comparisons locate 9 as the first element that fails that condition, and the returned position is four. The algorithm does not need to know the length of the duplicate run in advance.

Let me re-check the update in the other branch. When values[middle] > target, middle itself could be the first greater element. Setting high to middle retains that possibility as a boundary. Setting high to middle minus one would mix inclusive element rules with half-open interval rules and could skip the answer. The low update is different because a middle element at most target cannot itself be the boundary; the boundary must be after it, hence middle plus one.

Termination follows from an integer measure rather than a vague claim about halving. The width high - low is positive at loop entry. In the first branch, low becomes middle + 1, which is greater than its previous value and no greater than high. In the second branch, high becomes middle, which is smaller than its previous value and no smaller than low. Thus the nonnegative width strictly decreases. Its repeated approximate halving gives the logarithmic bound.

The contract assumes values is sorted in nondecreasing order. Checking that property inside the function would take linear time and defeat the stated search bound. I should document the precondition instead of quietly adding validation that changes the complexity. The elements and target also need consistent ordering: a pathological comparator or a floating NaN does not satisfy the ordinary sorted-order premise. No conversion is needed for normal integers, strings, or other values with a coherent total order.

There is no mutation. Only integer indices change, and each comparison reads one list element. The same function works with any sequence supporting constant-time indexing and length, such as a tuple. It would not be appropriate for a singly linked structure, because fetching the midpoint there is not constant time. That limitation comes from the chosen data representation, not from the boundary logic.

I will use the half-open formulation as the final function. It returns the same result as the draft while letting the loop endpoints carry the complete answer state.

For a small check, inserting 4 at the returned index in [1, 4, 4, 4, 9] places the new value after all existing fours and before nine. Searching for zero returns the beginning; searching for twelve returns the end. A singleton equal to the target returns one, which confirms that this is the boundary after equality rather than the boundary before it. The function uses O(log(n + 1)) comparisons and O(1) additional space. That is the completed implementation.

```python
def insertion_after_target(values, target):
    """Return the first index with value > target in a sorted sequence."""
    low, high = 0, len(values)
    while low < high:
        middle = (low + high) // 2
        if values[middle] <= target:
            low = middle + 1
        else:
            high = middle
    return low
```
