Thanks for your comments!
While it is OK for the synchronous init to touch nonisolated stored properties, that does not mean that it's safe for it to invoke a nonisolated method, since that method can create a task that captures the reference and begins running on another thread.
As of now, custom executors prevent the synchronous initializer (and deinit) from starting-off with actor isolation, because the executor for an exclusively-owned actor instance may be shared by other actors, and the executor may be busy during initialization (or deinitialization). We fundamentally cannot await to switch to that shared executor from a synchronous context, so we're left with the escaping-use restrictions detailed in the proposal.
I've been thinking about this "state switch" a bunch lately, but perhaps from a different angle than what you're suggesting. Describing the isolation of the synchronous init is hard, because its neither nonisolated nor actor-isolated. A synchronous init can maintain race-safety by enforcing exclusive ownership of the actor reference by the task that invoked the initializer, hence the escaping-use restriction. That restriction exists because we can't say that a particular actor instance is non-Sendable, as part of its type in the language.
But, we can say that an actor parameter has an isolation as part of its type. If we extend that more generally, I think one could describe the isolation of the actor reference in a synchronous init, even while it is in the "some assembly required" state. Imagine there is a third kind of isolation in the model, called task isolation, which says that exactly one task has a reference to the actor. That fact only guarantees safe access to the stored properties of the actor, because the actor's executor may be shared. In addition, that isolation status decays to being nonisolated after doing anything nonisolated, like passing it to a nonisolated method. If we were to model this imagined isolation's "decay" in SIL, we could then permit calls to nonisolated methods from a synchronous init:
actor A {
init(_ val: Int) {
self.x = val
if self.x == 0 {
self.x = 1
self.meth() // note: the nonisolated call
}
_ = self.x // error: cannot access stored properties on `self` after use in nonisolated call
Task { await self.iso() } // OK
}
nonisolated func meth() { /* ... */ }
func iso() { /* ... */ }
}
Users would just need to give up the capability to access stored properties after that call. This solves all of the issues in the "flow-sensitive isolation" described in the Alternatives Considered proposal. In particular, we now have a specific expression that causes the isolation to change.
I do plan to investigate this "decaying" isolation idea further, but did not include it in this revision of the proposal. Anyone have thoughts on whether it should be included?
I don't quite understand the danger with the init() above. Why does access to task-local values make this unsafe? Is it because one can sneak a non-Sendable value past Sendable checking on the arguments using a task-local value?
The fact that a discussion of Sendable-ness and what is permitted in a deinit is missing from the proposal is my mistake. When restructuring the proposal text to include the removal of global-actor isolation of stored properties, I forgot to merge in the remaining parts. Here is a summary of what I meant to convey about those topics:
Sendable-ness
The arguments to a non-delegating actor initializer must conform to Sendable. Delegating initializers can recieve non-Sendable types.
The reasoning here is that wrapping an existing class instance with an actor is not safe, since that reference may not be exclusive to the actor. Combined with the other rules about Sendable parameters and return values for actors, this means that reassigning a stored property of a non-Sendable type, like in init(s2:) above, should be safe.
Deinit
A deinit is in a very similar circumstance as an initializer: a deinit starts-off with exclusive access to the actor instance within a synchronous context, and must have access to its stored properties. A deinit cannot be async, because it can be invoked from anywhere. The idea of creating a new task to invoke the body of the deinit is covered in the Alternatives Considered.
Because of custom executors, exclusive access to the instance does not imply that we can freely invoke actor-isolated methods on the instance member. A MainActor-isolated class is one example of this. If the deinit were permitted to invoke one of the class's isolated methods without being on the executor, then a race can happen.
Just like a synchronous init, if we allow the actor self to be captured by a task, then a race can be observed in the deinit. Applying the escaping-use restriction to a deinit would solve this race. On the downside, that restriction would also prevent the capture of the actor instance in a task, so isolated methods cannot be invoked at all during or after a deinit. Programmers would need to pass the individual pieces of the actor instance's state to a nonisolated function that performs the tear-down, in order to share the tear-down code between the deinit and an isolated method. For now, I propose that the escaping-use restriction is applied to a deinit in order to make it race-free. We can later ease this restriction with better analysis (i.e., the "decay" of isolation I mentioned earlier), to enable task capture.
Here's an example of the kind of tear-down function that would be needed:
class Connection {}
actor A {
var connections: [Connection] = []
/* ... */
func shutdown() {
A.shutdown(self.connections)
}
deinit {
A.shutdown(self.connections)
}
static func shutdown(connections: [Connection]) {
for connection in connections {
// do shutdown for `connection`
}
}
}
Convenience inits became repurposed as a type of initializer that is truly nonisolated all the way through, as a way to workaround the escaping-use restrictions. Based on your suggestions, and after further investigation, I also learned that we can workaround any ABI breaks that I imagined, because there are no sub-actors that directly call the non-allocating entry-point of an actor's non-delegating initializer. 
Here's summary of what I think we should change in the proposal:
Delegating Initializers
Like structs and enums, an actor's delegating initializer does not require convenience, but if it does delegate, then it must delegate on all paths from the start of the initializer. A delegating initializer can be marked as nonisolated (or isolated to a global-actor), but does not have to be. If a delegating initializer is not marked as nonisolated, then the same rules that would apply had it been non-delegating, will also apply to the initializer.
Furthermore, a non-delegating initializer cannot be marked nonisolated. Reason: it really doesn't make sense for any non-delegating init to be nonisolated, because the under-construction instance's stored properties must be accessible without an await. Having nonisolated act differently only for initializers is inconsistent.