feat(base): handles hidden until their people go public, every answer about a new handle counted (ADR 0020, D5)

A handle's page and profiles.byHandle answer as for a handle nobody has
until its person goes public: a handle fixed by a publish, or a display
name, a bio, a link or an avatar (isPublic). A person's own profile, and
the host's own lookups (personWithHandle), are unchanged.

So every answer about a new handle now tells that an account has it,
and countHandleCheck counts them together on the register, 30 an hour
per person (or attempt) and 60 per network, refused beyond with
ProfileRefusal.tooManyHandleChecks: profile.checkHandle and saving a
profile with a handle new for the person, simpleLogin.checkHandle and
signUp, keys.set with a handle, and Halkyon Learn's inbox.block and
unblock, which look people up by handle. A person's own handle and what
is no handle aren't counted. The SimpleLogin limiters of commit 3 give
way to it, so a network's 60 are shared between people signed in and
people new. The register's checkHandle, saveProfile,
signUpWithSimpleLogin and setFirstKeys carry the visitor's network from
the member that asked.

Halkyon Learn's handle dialog words the new refusal (English, German).

Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-23 22:39:14 +02:00
commit 7eb1961b9f
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 674 additions and 134 deletions

View file

@ -287,8 +287,8 @@ class EndpointKeys extends _isc.EndpointRef {
/// ten minutes on this device, under [attempt]. Refused for an account
/// with keys (`PasswordRefusal.passwordExists`), a setup out of range
/// (`PasswordRefusal.invalidSetup`), a handle refused or missing
/// (`ProfileRefusedException`), and without that sign-in
/// (`OidcRefusal.unknownAttempt`).
/// (`ProfileRefusedException`; a handle counted as `profile.checkHandle`
/// counts it), and without that sign-in (`OidcRefusal.unknownAttempt`).
_ida.Future<void> set({
required _isc.UuidValue attempt,
required _iec2fqr0.KeysSetup setup,
@ -790,7 +790,8 @@ class EndpointProfile extends _isc.EndpointRef {
/// Saves your public profile, making it if you have none. Throws
/// [ProfileRefusedException] when it can't be saved (see
/// [ProfileRefusal]): a handle someone else has, whatever its case, or a
/// new handle once yours is fixed.
/// new handle once yours is fixed. A new handle counts as [checkHandle]
/// counts it.
_ida.Future<_i8qdmv6n.Profile> save(
String handle,
String displayName,
@ -823,9 +824,13 @@ class EndpointProfile extends _isc.EndpointRef {
/// Why [handle] couldn't be yours, as saving it would refuse it: not a
/// handle, reserved for the instance, someone else's whatever its case,
/// or a new one once yours is fixed; null when it can, as your own can. For
/// the app to say so while it is typed; saving checks again. Whose a handle
/// is, anyone can find out already ([ProfilesEndpoint.byHandle]), so this
/// tells nothing new.
/// the app to say so while it is typed; saving checks again. Handles stay
/// hidden until their people go public (Halkyon Learn's ADR 0020, D5), so
/// this tells that an account exists: at most 30 an hour for you and 60
/// from your network, with every other answer about a new handle (saving
/// one, signing up with one), refused beyond
/// ([ProfileRefusal.tooManyHandleChecks], thrown); your own handle and
/// what is no handle aren't counted.
_ida.Future<_ih722tf9.ProfileRefusal?> checkHandle(String handle) =>
caller.callServerEndpoint<_ih722tf9.ProfileRefusal?>(
'halkyon_base.profile',
@ -873,7 +878,10 @@ class EndpointProfiles extends _isc.EndpointRef {
String get name => 'halkyon_base.profiles';
/// The public profile with [handle], whatever its case. Throws
/// [ProfileNotFoundException] when there is none.
/// [ProfileNotFoundException] when there is none, and as well while its
/// person hasn't gone public (Halkyon Learn's ADR 0020, D5): published
/// nothing and left the rest of the profile empty (display name, bio,
/// links, avatar).
_ida.Future<_i8qdmv6n.Profile> byHandle(String handle) =>
caller.callServerEndpoint<_i8qdmv6n.Profile>(
'halkyon_base.profiles',
@ -931,9 +939,10 @@ class EndpointSimpleLogin extends _isc.EndpointRef {
/// said `newcomer`), could take [handle]: null where they could, else why
/// not, as saving a profile would say. For the app to say so while it is
/// typed; [signUp] checks again. At most 30 for an attempt and 60 from a
/// network in an hour (`ProfileRefusal.tooManyHandleChecks`, thrown):
/// each answer tells which handles exist. Refused
/// (`OidcRefusal.unknownAttempt`) where no identity waits.
/// network in an hour, with every other answer about a handle
/// (`ProfileRefusal.tooManyHandleChecks`, thrown): each tells which
/// handles exist. Refused (`OidcRefusal.unknownAttempt`) where no identity
/// waits.
_ida.Future<_ih722tf9.ProfileRefusal?> checkHandle({
required _isc.UuidValue attempt,
required String handle,
@ -952,9 +961,10 @@ class EndpointSimpleLogin extends _isc.EndpointRef {
/// keys [setup] describes, all made on the device beforehand; a session
/// for it here. Refused, the identity still waiting: for a setup out of
/// range (`PasswordRefusal.invalidSetup`) and a handle refused
/// (`ProfileRefusedException`, as saving a profile refuses it); for an
/// attempt unknown, taken or past its time (`OidcRefusal.unknownAttempt`),
/// and called signed in (`OidcRefusal.wrongCaller`).
/// (`ProfileRefusedException`, as saving a profile refuses it, the handle
/// counted as [checkHandle] counts it); for an attempt unknown, taken or
/// past its time (`OidcRefusal.unknownAttempt`), and called signed in
/// (`OidcRefusal.wrongCaller`).
_ida.Future<_iacc.AuthSuccess> signUp({
required _isc.UuidValue attempt,
required String handle,

View file

@ -45,8 +45,9 @@ enum ProfileRefusal implements _isc.SerializableModel {
/// The links were checked too often in the last hour.
tooManyLinkChecks,
/// The handles were checked too often in the last hour: their answers
/// tell which handles exist.
/// Too many answers about new handles in the last hour (checking them,
/// saving, signing up or blocking with them): each tells which handles
/// exist.
tooManyHandleChecks;
static ProfileRefusal fromJson(String name) {