Rename user archive action to deactivate

"Archive" was misleading for users: the action sets a profile to
DEACTIVATED while keeping the person in the organization. Rename it to
"deactivate" across the API, CLI, MCP, n8n, and console UI.

Consolidate the two overlapping operations into a single deactivateUser
backed by the fuller, guarded logic (SCIM guard, last-active-owner
guard, invitation expiry, signature cancellation, membership update,
webhook) and authorized via iam:membership-profile:deactivate. Remove
the archiveUser surface and the thin state-only deactivate path.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-07-28 17:30:14 +02:00
parent 99c3235b46
commit b10fc55b7f
14 changed files with 130 additions and 208 deletions

View File

@@ -605,13 +605,13 @@ func TestUser_RemoveUser_ProfileInUse(t *testing.T) {
assert.Contains(t, gqlErrors[0].Message, "referenced by other resources")
}
func TestUser_ArchiveUser(t *testing.T) {
func TestUser_DeactivateUser(t *testing.T) {
t.Parallel()
owner := testutil.NewClient(t, testutil.RoleOwner)
// Create a user to archive.
userToArchive := testutil.NewClientInOrg(t, testutil.RoleViewer, owner)
_ = userToArchive
// Create a user to deactivate.
userToDeactivate := testutil.NewClientInOrg(t, testutil.RoleViewer, owner)
_ = userToDeactivate
query := `
query($id: ID!) {
@@ -666,17 +666,17 @@ func TestUser_ArchiveUser(t *testing.T) {
require.NotEmpty(t, userID, "Should find viewer member")
mutation := `
mutation($input: ArchiveUserInput!) {
archiveUser(input: $input) {
archivedProfileId
mutation($input: DeactivateUserInput!) {
deactivateUser(input: $input) {
success
}
}
`
var mutationResult struct {
ArchiveUser struct {
ArchivedProfileID string `json:"archivedProfileId"`
} `json:"archiveUser"`
DeactivateUser struct {
Success bool `json:"success"`
} `json:"deactivateUser"`
}
err = owner.ExecuteConnect(mutation, map[string]any{
@@ -687,24 +687,24 @@ func TestUser_ArchiveUser(t *testing.T) {
}, &mutationResult)
require.NoError(t, err)
assert.Equal(t, userID, mutationResult.ArchiveUser.ArchivedProfileID)
assert.True(t, mutationResult.DeactivateUser.Success)
err = owner.ExecuteConnect(query, map[string]any{
"id": owner.GetOrganizationID().String(),
}, &result)
require.NoError(t, err)
var archivedUserState string
var deactivatedUserState string
for _, edge := range result.Node.Profiles.Edges {
if edge.Node.ID == userID {
archivedUserState = edge.Node.State
deactivatedUserState = edge.Node.State
break
}
}
require.NotEmpty(t, archivedUserState, "Should still find archived user")
assert.Equal(t, "DEACTIVATED", archivedUserState)
require.NotEmpty(t, deactivatedUserState, "Should still find deactivated user")
assert.Equal(t, "DEACTIVATED", deactivatedUserState)
}
func TestUser_DeactivateUserCancelsSignatureRequests(t *testing.T) {