A custom input port needs to buffer at least one item, to support peek-* operation.
get-position handler must return whatever position marker of the source (that is, the position the next read! will occur). it may or may not corresponds to port-position returns. It doesn't have to know what port-position returns.
A custom textual input port must have a slot to keep an underlying position value returned by get-position.
peek-char on a custom port must call get-position first, and remember its value, then call read! to read just one character. Other read operations clear underlying position value.
port-position returns an implementation-specific value embedding the underlying position value. If there's a value in underlying position value, then it returns a position value embedding it. Otherwise, it calls get-position to obtain the current position.
If my understanding is correct, I'd write up a paragraph or two explaining this. I don't like that it incurs overhead on each peek-char and it prevents any further buffering, but at least it's portable.