---
id: w4/m12/t001
title: Membership verbs in Core — list/invite/change-role/remove, tuples + rows atomic
worker: worker4
status: done
estimate: 35m
depends_on: [w1/m9/t004]
---

## Objective

Add the membership verb set to the backend: `ListMembers`, `InviteMember(email, role)`, `SetMemberRole`, `RemoveMember` — each writing the `tenant_members` row and the corresponding OpenFGA tuple (`workspace:tea-<id>` relations per Render's role matrix) so the store and the authz graph can't drift; refuse operations that would leave a workspace without an admin.

## Context

- w1/m9 establishes the substrate this builds on: tenant rows, `tenant_members`, key→tenant binding, workspace-scoped `core.Authorize`. These verbs are the *management* layer over it.
- Role names and the permission matrix are already mapped in `deploy/gitops/authz/model.fga` + docs/ADR012-auth.md (viewer/contributor/developer/admin/billing) — reuse those relation names verbatim.
- Row + tuple must move together: write the row, then the tuple, with a reconciliation path for a crash between the two (or a transactional outbox — pick the simplest credible mechanism and document it).
- Authorization on the verbs themselves: member management requires the admin relation (`can_manage`-tier), checked by the same OpenFGA checker as every verb.

## Steps

1. Verbs in a `members` area of the store-backed feature layer; `tenant_members` writes + FGA tuple writes.
2. Last-admin guard: `RemoveMember`/`SetMemberRole` refuse if the target is the only admin.
3. Typed errors: unknown member, duplicate invite, insufficient role.

## Files

- `lego/backend/internal/` (members feature or extension of the store/tenancy layer per w1/m9's final layout)
- `lego/backend/internal/store/` (member queries)

## Acceptance criteria

- [ ] Each verb updates row + tuple; a subsequent authz check reflects the change with no cache staleness beyond the documented TTL.
- [ ] Last-admin removal/demotion refused with a typed error.
- [ ] Non-admin callers get 403 on all four verbs.

## Out of scope

- Email delivery (t002); HTTP surfaces (t003); billing-role semantics beyond the FGA relation (no billing system yet).
