A grain that deactivated is no longer reported as a delivery failure

The router's delivery classifier already had a rule, and it is a good one: only conditions that are a lifecycle transition by construction are reported to the sender as transient, and anything it does not recognise stays terminal, so a genuine defect is still reported as one.

Two such conditions were recognised — the grain directory mid-handoff, and the host going away. A third was not: Orleans deactivating an idle grain. A message in flight to it is forwarded, and when the forwards are exhausted Orleans rejects with

… for 2 times after "DeactivateOnIdle was called." to invalid activation. Rejecting now.

which is a lifecycle transition by exactly the same argument — the grain-level analogue of the host going away, and the target re-activates on the next call. Reported as a terminal failure it tore down every consumer carrying its own recovery machinery, which is the damage already removed for the silo-level shapes and left standing for this one. Measured on 2026-09-17: 191 occurrences against a single address.

The same sentence means two opposite things, so the text alone cannot decide

A grain whose activation always faults — a broken NodeType compile that cannot produce a hub configuration — has an alive window of about zero, so every delivery lands in a deactivation window and Orleans emits the same rejection. That grain never comes back, and calling it a lifecycle transition would hide a real defect behind a transient answer.

The registry the platform already keeps for this is the discriminator: the grain records its true activation error on every faulted activation, so the absence of a recorded error is what separates "deactivated on idle, will be back" from "cannot activate at all". Where a caller cannot consult it, the verdict stays terminal — the worse case is the default.

The signal is also Orleans' own text, and that is a known weakness

There is no typed rejection reason to read — the rejection type carries every kind of refusal, so accepting it alone would classify genuine refusals as transient, which is the one direction the classifier says must not happen. The test is therefore the narrowest phrase that means the activation is gone.

If Orleans re-words that message, this quietly returns to the previous answer rather than misclassifying anything — the safe direction to fail in. The test beside it pins the production string verbatim rather than a paraphrase, and the opposite direction is pinned too: an ordinary rejection, an unrelated fault and a timeout all stay terminal. A timeout especially — a target that did not answer across the whole budget is plausibly wedged rather than restarting, and demoting it would produce a resubscribe storm against a hub that never comes back.

Reconnecting…
The connection to the server was interrupted. Trying to restore it…
Trying again…
The connection could not be restored. Reloading the page…
The server was updated. Reloading the page to pick up the latest version.