DeviceRegistrationAuthorizer
The host application's decision whether the caller of a registration may add a device to a user (docs/server-authentication.md, "Registration authorization"). KSecureMessage has no account system: it cannot know who owns a dev.kreienbuehl.ksecuremessage.model.UserId. Every first registration of a device address, including the first device of a user, needs this authorizer's DeviceRegistrationAuthorizationResult.Authorized in addition to the proof that the registrant holds the device authentication key.
C is the host's own request authentication context: the authenticated account principal, a session, an OAuth subject, an enrollment grant, whatever the host established for the request before KSecureMessage saw it (S1.1, finding N1). The HTTP adapter obtains it from a host-supplied extractor; a request without one is denied before the authorizer is called. KSecureMessage never interprets, logs, serializes, stores or returns the context and never puts it into a signed or hashed input: it only passes it to authorize within the one request.
The decision must rest only on C, the request and state the host owns (its account and device records). KSecureMessage deliberately passes no view of its own registrations: "the user has no devices yet" is not ownership, and a read outside the registration's atomic write would be stale anyway (S1.1, finding N2).
Registered devices of a user are authorities for that user: they can authorize a device recovery (docs/device-recovery.md), register, rotate and revoke the user's offline recovery key and manage its reset. Those flows trust exactly the membership this authorizer approved.
The authorizer is called only when the address has no registration yet; a retry with the registered key and a request with another key (a conflict) never reach it. It is called before anything is stored and outside every storage transaction. Anything it throws rejects the registration as a server failure (DeviceRegistrationAuthorizationFailedException), exceptions and errors such as AssertionError or NotImplementedError alike; its message is never sent to the client (S1.2, finding N4). Only genuine cancellation of the calling coroutine and the process-health failures OutOfMemoryError and StackOverflowError pass through unchanged; a CancellationException thrown while the coroutine is still active, InternalError, UnknownError and every other Throwable are sanitized like any other failure (S1.3, finding N7).