ADR-031: The User

Date: 2026-08-18 Status: Stub — the statements below are made. Nothing beyond them is decided.

Statements

The name

A User has a display_name, and it is a name rather than half a login. Not unique, never asked for at a door, no password beside it. What identifies a User is a key they hold or an account they proved.

"display_name of root cannot be changed anymore when set"

"if display_name of root is unset, it becomes root"

"regardless of root_identity setting it or not, root is never an available display name except for the one root identity user, they dont need to set their display name as root"

So the ROOT User is root from the moment they exist, without choosing it, and root is the one name no other User may take.

What is recorded

A User is one object per person under <location>/system/users/ on the parquet backend, holding an id, a display_name, any number of emails, the level, the keys it holds and the accounts that reach it. A sqlite deployment keeps none, the way it keeps no tokens.

POST /auth/user/arrive is where a person says a name and an email, and requires neither. The name is settled once; an email that arrives later is added rather than refused, because a User has any number of them.

You claim the ROOT User by proving you own a root identity. It happens once and cannot happen again, so it is attested as node:claimed. Recording never fails the thing it records (ADR-030), which makes this the one attestation whose loss cannot be made up later.

Not done

A User holds no last name and no phone number.

A User reaches namespaces through permission, and nothing records which yet.

A namespace's owner is a string, and so is a credential's admitted_as.

created_by is empty on ROOT. The node that signed the first admission is written down — node:claimed names it — but on the attestation rather than on the User, so the provenance is a record to go and find instead of a field to read.

system holds the User records, and only the parquet backend has namespaces at all (ADR-026, "Not done") — so on SQLite there is nowhere for them to go yet.