```python
"""One point-of-sale till session, replayed from its events."""

OPEN = "open"
PAYMENT = "payment"
PAID = "paid"
CANCELLED = "cancelled"


def run_till_session(events, prices):
    cart = {}
    total = 0
    paid = 0
    change = 0
    session_state = OPEN

    for event in events:
        action, item = event[0], event[1]

        if action == "scan":
            if item not in prices:
                raise ValueError("item not in price list: " + item)
            if item in cart:
                cart[item] += 1
            else:
                cart[item] = 1
            total += prices[item]

        elif action == "void":
            if item not in cart:
                raise ValueError("item not in cart: " + item)
            if cart[item] == 1:
                del cart[item]
            else:
                cart[item] -= 1
            total -= prices[item]

        elif action == "close":
            if session_state != OPEN:
                raise ValueError("session is not open: " + str(session_state))
            if cart:
                paid += total
                change = total
            session_state = PAID

        elif action == "pay":
            if session_state != PAID:
                raise ValueError("session is not paid: " + str(session_state))
            if paid < total:
                raise ValueError("amount paid is less than total: " + str(paid))
            change = paid - total
            session_state = PAID

        elif action == "cancel":
            if session_state != OPEN and session_state != PAID:
                raise ValueError("session is not open or paid: " + str(session_state))
            session_state = CANCELLED

        else:
            raise ValueError("unknown action: " + action)

    return {
        "items": sorted(cart.items(), key=lambda x: x[0]),
        "total": total,
        "paid": paid,
        "change": change
    }
```