Add owner/operator/viewer roles via a signer_members table with a RolesGuard, enabling team delegation and ownership transfer. ## Acceptance Criteria - [ ] `signer_members` table exists with proper indexes and RLS policies - [ ] All existing signers have their `owner_address` seeded as `owner` role - [ ] `@Roles('owner')` blocks operators and viewers with 403 - [ ] `@Roles('operator')` allows operators and owners, blocks viewers - [ ] `@Roles('viewer')` allows all three roles - [ ] API key auth bypasses role checks entirely (backward compatible) - [ ] `POST /signers/:id/members` allows owner to add members - [ ] `DELETE /signers/:id/members/:memberId` prevents removing the last owner - [ ] `POST /signers/:id/transfer-ownership` atomically swaps ownership - [ ] `GET /signers` returns all signers the user has any membership in - [ ] Audit log shows entries for all signers the user has membership in - [ ] Operator can override-sign but cannot modify policies - [ ] Viewer can view but cannot pause/sign/modify - [ ] Frontend conditionally renders action buttons based on role - [ ] No breaking changes to existing auth flows ## Tasks - [ ] Database migration: `signer_members` table + seed existing owners - [ ] `SignerMemberRepository` (Supabase CRUD) - [ ] `@Roles()` decorator + `RolesGuard` implementation - [ ] `MemberService` (add, remove, transfer, list) + `MemberController` - [ ] Apply `@Roles()` to `SignerController` and `SigningController` endpoints - [ ] Update `GET /signers` and audit log filtering to use memberships - [ ] Update RLS policies - [ ] Unit + integration tests - [ ] Frontend: conditional rendering + members management panel
Add owner/operator/viewer roles via a signer_members table with a RolesGuard, enabling team delegation and ownership transfer.
Acceptance Criteria
signer_memberstable exists with proper indexes and RLS policiesowner_addressseeded asownerrole@Roles('owner')blocks operators and viewers with 403@Roles('operator')allows operators and owners, blocks viewers@Roles('viewer')allows all three rolesPOST /signers/:id/membersallows owner to add membersDELETE /signers/:id/members/:memberIdprevents removing the last ownerPOST /signers/:id/transfer-ownershipatomically swaps ownershipGET /signersreturns all signers the user has any membership inTasks
signer_memberstable + seed existing ownersSignerMemberRepository(Supabase CRUD)@Roles()decorator +RolesGuardimplementationMemberService(add, remove, transfer, list) +MemberController@Roles()toSignerControllerandSigningControllerendpointsGET /signersand audit log filtering to use memberships