0019. Capabilities can be granted to a membership level
- Status: accepted
- Date: 2026-10-28
Context
Capabilities (#28) were granted only to individual people, and the rule was that nothing implies one: not a tier, not a role, not being an admin. Some capabilities are meant for everyone at a level, for example ha-lights for every member. Granting one to each member separately is tedious, is easy to forget for a new member, and leaves grants behind when someone stops paying (they become inactive at guest, but stay in the file).
Decision
- The members file has an optional
tiers:section, withfriendandmemberkeys, each holdingcapabilitieslike a person's entry.guestandadminare rejected: a guest never passes a capability check, and admin status still implies nothing. - A level grant reaches that level and every level above it (a
friendgrant reaches members too), so the same ordering applies as forminTier. - It follows the person's membership level in
members.yaml, not their effective tier. An admin gets a level's capabilities through their own members entry (usuallymember) and gets nothing from being an admin. An admin whose entry isguestgets none. - What a person holds is their own grants plus their level's.
StoreCapabilitySourcecombines them, and the dispatcher's check doesn't change: bothminTierand the capability must hold, and a guest gets nothing. - Changes go through the
AccessStore(setTierCapabilities), with the same steps asapply: re-read, validate, edit the YAML document, atomic write with.bak, then swap the view and write anaccess.changedaudit record (kind: set-tier-capabilities,target: tier:<level>, before and after). The admin commands are/admin capabilities grant-levelandrevoke-level. - Nobody is DMed about a level change. It can reach every member at once, and the per-person DM was there to tell someone a grant was made for them.
- Any registered capability can be granted to a level, including
ha-doorsandha-admin. That is a choice for admins to make, not something the code forbids.
This supersedes the "nothing implies a capability: not a tier" rule in identity-and-access.md and AGENTS.md rule 11, for grants made to a level. A capability still never comes from being an admin or from a Discord role.
Consequences
One admin command can give a capability to everyone at a level. Granting
ha-doorstomemberwould let every member open the building, and a taken-over account of any member could too (seehome-assistant.md). The audit record and/admin capabilities listshow it, andrevoke-leveltakes it back at once.Revoking from a level doesn't touch individual grants, so someone granted a capability on their own keeps it.
/admin capabilities revokeon someone who has it only from their level says so and changes nothing. (Superseded by ADR 0024: it now revokes it from that person alone.)A person's capabilities now depend on their level: demoting a member to friend takes away the member level's grants straight away, and promoting a friend gives them.
members.yamlis still the one source, so a hand edit followed by/admin reloadworks as before, and unknown names intiers:are warned about and ignored like a person's.