ADR-0010: Commands bounded by what their maker holds¶
Status: Accepted Date: 2026-10-12 Deciders: Alex Nodeland
Context¶
Lattice RFC-0001 gives Grants its reads and says that no one grants more than they hold, that operators are granted by operators and the first by make operator, and that grantr's admin router checks its callers with grantr's own actions. Phase 2's exit criteria need the commands that change access before the router exists, and leave open where the bound applies beyond a grant, what a member is, and how the first operator is made.
Decision¶
Grantsholds the commands, each madebya subject and checked with grantr's own action on the resource it changes: tenants, members, groups, custom roles, grants, keys and operators. Phase 3's router calls them, as the libraries' surfaces call their workspaces.- The bound applies to every change that gives or takes access, where it applies: a grant's or revocation's role on its resource; each grant a removed member loses; the grants a group carries, when its members change or it is deleted; and the grants a principal holds, when a key is issued to act as it or revoked. So a tenant admin can't make themselves an owner by joining a group, or mint or revoke a key for an owner's principal. A custom role holds only workspace-level actions, which everyone who may define one holds, so defining one is bounded by construction. ADR-0014 adds an operator's own grant, giving up one's own grant, and keeping a tenant's last owner.
- A member is whoever holds the
membergrant on the tenant.add_memberandremove_memberare its only paths, andgrantandrevokerefuse it. Roles are granted only to members, groups hold only members, and removing a member takes its grants, its places in groups and its keys. A new tenant's owner is made a member and an owner. - The first operator is made by
first_operator(user), refused once any operator exists, and audited with no maker. Other operators are granted and revoked bygrant_operatorandrevoke_operator, which an operator calls;grantrefuses the deployment. - Each command is one transaction: its checks, its change and its audit entry. The maker's action is checked first, so a refusal says nothing about what exists, and a tenant that a maker holds an action in exists.
Options considered¶
| Option | For | Against |
|---|---|---|
Commands on Grants, each bounded where it gives or takes access (chosen) |
One place enforces the rules, for the router, the template's make targets and tests |
Grants is larger than the RFC's sketch of its reads |
| Commands only in phase 3's router | Grants stays read-only |
The rules would live in a router, and the exit criteria couldn't be tested before it |
The bound on grant alone, as the RFC words it |
Fewer checks | Group membership and keys would each be a way around it |
| Membership as any grant in the tenant | No member role to manage |
Revoking a user's last role would silently end their membership, and a member with no roles couldn't exist |
Consequences¶
- Easier: the router in phase 3 is a thin surface, and
make tenantandmake operatorcall the same commands. - Harder: a command that changes a group or a key checks every grant it affects.