I will use a sorted sweep. The input is a collection of closed integer intervals, and the requested output is their union represented by disjoint closed intervals. Sharing an endpoint counts as overlap, so [2, 5] and [5, 8] become [2, 8]. Merely being adjacent does not: [2, 5] and [6, 8] remain separate under this definition. That distinction belongs in the comparison itself, because adding one to the right endpoint would quietly solve a different problem.

The sweep needs to remember only the interval currently being assembled. Once intervals are sorted by their left endpoint, an incoming interval either overlaps that active interval or starts after it ends. In the second case, all later intervals also start after the active interval ends. It is therefore safe to append the active interval to the output permanently. No future interval can bridge backwards across a gap, because its start cannot be smaller than the start currently being processed.

I should account for contained intervals carefully. Suppose the active interval is [1, 12] and the incoming interval is [3, 7]. Replacing the active right endpoint with 7 loses coverage. Taking the maximum preserves 12, which is why the update cannot simply copy the incoming endpoint. Equal starts create the same issue when the sort places a shorter interval after a longer one. Sorting whole pairs is fine because the maximum operation handles either ordering among equal starts.

There is also an input ownership question. Sorting the caller's list in place would change its ordering, and updating nested lists could change the caller's interval values. I can avoid both effects by constructing tuples while reading the iterable and by placing newly created lists in the result. The function will accept an iterable of pairs, including a generator, because the sorted sweep already requires materializing the input. That has the same asymptotic space cost as sorting a copied list.

The function below validates reversed endpoints rather than normalizing them. If a caller passes [9, 2], silently swapping it would conceal a mistaken input. The examples only use valid integer intervals, but checking the ordering keeps the contract explicit. I will not add unrelated type conversion: converting strings or floating values to integers would change their meaning, while Python can already compare ordinary numeric endpoint types that support the required ordering.

The empty input should return an empty list. It is tempting to initialize from the first element unconditionally and catch an index error, but an explicit empty check is clearer and leaves unexpected exceptions visible. A singleton input returns one fresh pair. Repeated intervals collapse into one interval naturally, without a separate deduplication pass. Negative endpoints and intervals crossing zero need no special treatment because the sweep uses comparisons rather than indexing by endpoint value.

Here is the implementation, with verification comments describing the invariant at each decision rather than listing many individual assertions.

To check correctness, first consider coverage preservation. Initially the active pair covers exactly the first sorted interval. An overlapping interval contributes no point to the left of the active start, and every point it contributes on the right is accounted for by the maximum endpoint. Thus merging preserves exactly the union of that component. At a gap, appending the active pair preserves coverage already accumulated and starts a new component with exactly the incoming interval. At termination, the final append accounts for the remaining active component.

The output is ordered and disjoint for a separate reason. A component is finalized only when the next left endpoint is strictly greater than its right endpoint. The next component begins at that left endpoint, and subsequent merges can only extend its right side. They cannot move its start backwards. Therefore each output pair remains separated from the preceding pair, and every emitted start is at least the previous start. This establishes the output representation requirement in addition to preservation of the underlying set of points.

For a concrete nested example, sorting [(7, 9), (1, 20), (3, 4)] gives [(1, 20), (3, 4), (7, 9)]. Both later left endpoints are at most 20, so neither branch emits a partial interval. The right endpoint stays 20 after both maximum operations. By contrast, [(1, 3), (6, 8), (8, 11)] emits [1, 3] at the first gap and then merges the latter two intervals into [6, 11]. These examples exercise different transitions without needing a large wall of tests.

The running time is O(n log n) for sorting and O(n) for the sweep. The copied intervals and the result use O(n) space. If the input were already sorted and the caller guaranteed that property, a streaming variant could avoid the sort, but this function intentionally handles arbitrary ordering. The implementation above is the final answer for closed intervals with overlap at a shared endpoint.

```python
def merge_closed_intervals(intervals):
    """Return the union of valid closed intervals without modifying the input."""
    ordered = []
    for left, right in intervals:
        if left > right:
            raise ValueError("an interval starts after it ends")
        ordered.append((left, right))
    ordered.sort()
    if not ordered:
        return []

    # At entry to the loop, [active_left, active_right] is the union of
    # one connected group among the intervals already visited. Everything
    # in result precedes it and is separated from it by a genuine gap.
    # The result entries are newly allocated pairs, so neither sorting nor
    # extending an active interval can mutate a pair supplied by the caller.
    active_left, active_right = ordered[0]
    result = []
    for left, right in ordered[1:]:
        # The sort guarantees left >= active_left. Consequently a new
        # interval can only extend coverage to the right or leave it alone.
        # When left equals active_right, both closed intervals contain that
        # endpoint, so they belong to the same connected union component.
        # Taking max also handles a short interval nested entirely inside
        # the current component, which must never shorten its coverage.
        if left <= active_right:
            active_right = max(active_right, right)
        else:
            # The strict inequality certifies a gap. Every unvisited start
            # is at least left, so no later interval can intersect the
            # active component and make this append premature. We can
            # finalize this component once and never reconsider it.
            result.append([active_left, active_right])
            active_left, active_right = left, right

    # A component is appended at the next gap, so the last one has no loop
    # iteration available to flush it. This append also covers the singleton
    # case and the case where all intervals merged into one large component.
    result.append([active_left, active_right])
    return result


if __name__ == "__main__":
    examples = [
        ([], []),
        ([(5, 8), (1, 4), (4, 6)], [[1, 8]]),
        ([(1, 12), (3, 7), (15, 15)], [[1, 12], [15, 15]]),
        ([(-4, -1), (0, 2)], [[-4, -1], [0, 2]]),
    ]
    for source, expected in examples:
        actual = merge_closed_intervals(source)
        print(source, actual, actual == expected)
```
