Just realized that CpsFlowExecution.interrupt only calls stop on getCurrentExecutions, which by its Javadoc only includes innermost executions. And this makes sense to me: it is only necessary to interrupt the stuff that is actually running, and if that throws up a FlowInterruptedException, then BodyExecutionCallback.onFailure should be called and the outer step should fail too.
If that is right, then the current stop method is totally wrong: it should not call cancel on anything, and in fact it should be a no-op because it should never be called. (Or if it is called, it would be as a race condition between retries, meaning all it needs to do is immediately getContext().onFailure(cause).) But then Callback.onFailure needs to check for t instanceof InterruptedException and throw that up without retrying. And other stop methods on block-scoped steps are probably incorrect too.
The correct handling of interruption by steps remains very unclear to me. There needs to be a definitive document explaining how interruption works and what steps are supposed to do in order to participate properly. And we need tests that actually trying interrupting flows at various points.