@@ -42,10 +42,13 @@ const USER_ID_OPS = [
4242 'gitlab_delete_user_identity' ,
4343]
4444
45- /** Operations that take a named access-level dropdown (required unless noted). */
45+ /**
46+ * Operations that take the shared access-level dropdown (required unless
47+ * noted). Update Member deliberately has its own dropdown without a default so
48+ * an expiry-only edit cannot silently reset a Maintainer/Owner to Developer.
49+ */
4650const ACCESS_LEVEL_OPS = [
4751 'gitlab_add_member' ,
48- 'gitlab_update_member' ,
4952 'gitlab_invite_member' ,
5053 'gitlab_approve_access_request' ,
5154 'gitlab_add_saml_group_link' ,
@@ -54,7 +57,6 @@ const ACCESS_LEVEL_OPS = [
5457/** Operations where the access level is required (approve/invitation update are optional). */
5558const ACCESS_LEVEL_REQUIRED_OPS = [
5659 'gitlab_add_member' ,
57- 'gitlab_update_member' ,
5860 'gitlab_invite_member' ,
5961 'gitlab_add_saml_group_link' ,
6062]
@@ -1033,6 +1035,30 @@ Return ONLY the commit message - no explanations, no extra text.`,
10331035 value : ACCESS_LEVEL_OPS ,
10341036 } ,
10351037 } ,
1038+ // Access level for Update Member. Required by GitLab's edit-member API, but
1039+ // deliberately has NO default: the user must explicitly pick the level so an
1040+ // expiry-only edit can't silently downgrade a Maintainer/Owner to Developer.
1041+ {
1042+ id : 'memberAccessLevel' ,
1043+ title : 'Access Level' ,
1044+ type : 'dropdown' ,
1045+ options : [
1046+ { label : 'No access' , id : '0' } ,
1047+ { label : 'Minimal Access' , id : '5' } ,
1048+ { label : 'Guest' , id : '10' } ,
1049+ { label : 'Planner' , id : '15' } ,
1050+ { label : 'Reporter' , id : '20' } ,
1051+ { label : 'Security Manager' , id : '25' } ,
1052+ { label : 'Developer' , id : '30' } ,
1053+ { label : 'Maintainer' , id : '40' } ,
1054+ { label : 'Owner' , id : '50' } ,
1055+ ] ,
1056+ required : true ,
1057+ condition : {
1058+ field : 'operation' ,
1059+ value : [ 'gitlab_update_member' ] ,
1060+ } ,
1061+ } ,
10361062 // Optional access level for Update Invitation. Defaults to "Leave unchanged"
10371063 // so updating only the expiration does not silently reset the access level.
10381064 {
@@ -1818,15 +1844,15 @@ Return ONLY the commit message - no explanations, no extra text.`,
18181844 }
18191845
18201846 case 'gitlab_update_member' :
1821- if ( ! params . resourceId ?. trim ( ) || ! params . userId || ! params . accessLevel ) {
1847+ if ( ! params . resourceId ?. trim ( ) || ! params . userId || ! params . memberAccessLevel ) {
18221848 throw new Error ( 'Project / Group ID, User ID, and Access Level are required.' )
18231849 }
18241850 return {
18251851 ...baseParams ,
18261852 resourceType : params . resourceType || 'project' ,
18271853 resourceId : params . resourceId . trim ( ) ,
18281854 userId : Number ( params . userId ) ,
1829- accessLevel : Number ( params . accessLevel ) ,
1855+ accessLevel : Number ( params . memberAccessLevel ) ,
18301856 expiresAt : params . expiresAt ?. trim ( ) || undefined ,
18311857 memberRoleId : params . memberRoleId ? Number ( params . memberRoleId ) : undefined ,
18321858 }
@@ -2130,6 +2156,10 @@ Return ONLY the commit message - no explanations, no extra text.`,
21302156 groupId : { type : 'string' , description : 'Group ID or URL-encoded path' } ,
21312157 userId : { type : 'number' , description : 'Target user ID' } ,
21322158 accessLevel : { type : 'number' , description : 'GitLab access level (10-50)' } ,
2159+ memberAccessLevel : {
2160+ type : 'string' ,
2161+ description : 'Access level for member updates (explicit choice, no default)' ,
2162+ } ,
21332163 invitationAccessLevel : {
21342164 type : 'string' ,
21352165 description : 'Optional new access level for an invitation ("" leaves it unchanged)' ,
0 commit comments