The Address String Form

The rule: Address.ToString(), Address.ToFullString(), the implicit string conversion and the JSON AddressConverter all write ONE form: outermost_host~…~host~path. For an unhosted address that is just its Path. The implicit string → Address parser reads that same form back, so (Address)address.ToString() equals address, including nested hosts.

Why this needed fixing (#6604)

Until #6604, ToString() rendered path~host, but the parser splits on the FIRST ~ and treats the left side as the host. Every key minted with ToString() and then parsed back therefore came back REVERSED. sync/g~ui/c became ui/c hosted on sync/g. The keys that do this round trip are:

Neither raised an error. The pod-hub grain simply looked up the wrong local route, answered PodHubNotHere, and the router fell back.

The parser, the wire serializer, AccessContext.ObjectId and UserNodeType.IsPortalIdentity all used host~path already. ToString() was the outlier, and it now delegates to ToFullString().

Rules for callers

The one text that crosses a roll

Grain keys and memory-stream ids are in-memory only, so nothing persisted used the old form. The one cross-process TEXT that did is the shutdown banner, Hub {subject} is shutting down, inside a NACK message. During a roll an old pod's NACK is read by a new pod. So:

Where it is pinned