@LeeCampbell: At this point, there are no magic tricks to know whether an operator is part of a "safe" chain. What's really going on here is a different approach to writing operators, based on custom observer implementations rather than the anonymous implementation approach through lambda-based Subscribe calls to the sources of the operator.
In order to ensure "global" safety, there's a minimal amount of cheap safeguarding based on swapping out observers for mute ones as soon as an operator's input reaches a terminal state. So, multiple terminal messages (invalid from the grammar's point of view) are silenced but the first one, and the place this happens is inside the custom operator noticing this (rather than in the Subscribe method's wrapping of the observer in the past).
As for concurrent notifications (which are invalid too), there has never been true safeguarding against this, so inputs are assumed to be safe. Only when an operator has to deal with multiple sources, it will do proper orchestration internally (e.g. Merge has to ensure we never talk to the outgoing observer in a concurrent manner), using locks or other approaches. However, when an ill-behaved producer is in the mix, behavior is undefined (e.g. a Subject<T> that exhibits concurrent messages). In case such a producer exists, one has to safeguard the pipeline using Synchronize.
When implementing sources or custom operators, we recommend to use ObservableBase<T> and Observable.Create, respectively. Those are the safest way to get things right. In fact, for custom operators, the use of composition of existing operators is a good start. Going forward, we may provide more advanced "power user" facilities to write more efficient custom operators using techniques similar to the ones we're using internally now. However, with power comes responsibility, so users of such primitives and implementation patterns need to have a thorough understanding of expectations with regards to observable sequences, the observer grammar, etc.