Provisionamento reversível de usuários SaaS por agentes de IA
Projete o provisionamento reversível de usuários SaaS separando convites, funções e grupos, com novas tentativas seguras e auditoria útil.

Um agente de IA nunca deveria provisionar um usuário SaaS com uma instrução opaca como «adicione Priya à conta da empresa com o acesso normal da engenharia». Essa frase esconde pelo menos três mudanças de estado: criar ou convidar uma identidade, atribuir uma função no nível da conta e adicionar associações a grupos. Cada mudança tem risco, condição de conclusão e operação de desfazer próprios.
O provisionamento reversível de usuários SaaS começa preservando esses limites. O agente propõe uma sequência, executa uma chamada por vez, registra os identificadores retornados e para quando o estado observado difere do esperado. Isso custa algumas chamadas extras à API, mas evita que o operador reconstrua uma concessão incompleta depois de um timeout, um email errado ou uma associação ampla demais.
Convite, função e associação são estados diferentes
Um convite pendente não é um usuário, um usuário não é uma função e uma função não é uma associação a grupo. Sistemas de provisionamento costumam misturar esses objetos porque os consoles dos fornecedores os exibem em um único formulário. A API por baixo frequentemente expõe recursos ou operações de ciclo de vida separados, e essa diferença determina se o agente consegue se recuperar com segurança.
O convite normalmente representa uma intenção junto de um processo de entrega ou aceite. A pessoa pode precisar aceitá-lo, usar outra identidade ou nunca responder. O Microsoft Graph documenta isso para usuários externos: a criação retorna um objeto de convite e a pessoa conclui um fluxo interativo. No GitHub, a associação à organização também permanece pendente até o aceite. Um agente que registra «usuário criado» logo após o convite anotou uma expectativa, não um fato.
Uma função altera a autoridade na conta ou organização. Ela pode tornar alguém proprietário, responsável por cobrança, administrador, convidado ou membro comum. Um grupo costuma conceder acesso indireto a projetos, repositórios, canais, aplicativos ou dados compartilhados. Remover o grupo pode revogar esse acesso sem mudar a função da conta; rebaixar a função pode deixar intactos os privilégios herdados de grupos.
Modele os estados separadamente, mesmo quando o fornecedor oferece um endpoint que recebe os três em um único POST. Um registro interno sensato se parece com isto:
{
"subject": "[email protected]",
"invitation": {"state": "pending", "id": "inv_8421"},
"role": {"desired": "member", "observed": null},
"groups": {
"desired": ["engineering", "on-call-readers"],
"observed": []
}
}
A separação responde à pergunta operacional desconfortável: o que exatamente precisa ser desfeito? Se o convite existe, mas a conta ainda não foi aceita, cancele o convite. Se a conta tem a função errada, restaure a função anterior. Se um grupo foi adicionado e a chamada seguinte falhou, remova apenas a associação criada nesta execução. Excluir o usuário inteiro costuma ser uma substituição imprudente para saber qual estado mudou.
A primeira regra de projeto é simples: uma ação no diário deve corresponder a uma transição de estado observável no sistema remoto. A chamada ainda pode causar efeitos laterais, como email, mas agente e operador devem conseguir nomear a transição principal sem acrescentar «e também fez... ».
O plano de provisionamento deve ser dado, não prosa
O agente deve compilar o pedido humano em um plano tipado antes de chamar o fornecedor. O plano expõe suposições ambíguas e oferece entradas estáveis ao executor. O raciocínio livre pertence ao momento anterior à execução; a fronteira de execução deve receber dados simples e precisos.
No mínimo, o plano precisa do identificador do sujeito, tenant de destino, modo de convite, função e grupos solicitados, precondições e identificador da operação. Também deve dizer se a ação pode enviar email. A entrega do convite é um efeito externo que o cancelamento não recolhe, portanto escondê-la em um padrão é uma prática ruim.
{
"operation_id": "prov_2026_07_24_0187",
"tenant": "acme-production",
"subject": {"email": "[email protected]"},
"steps": [
{"kind": "invite", "send_email": false},
{"kind": "wait_for_acceptance"},
{"kind": "set_role", "role": "member"},
{"kind": "add_group", "group": "engineering"},
{"kind": "add_group", "group": "on-call-readers"}
],
"preconditions": {
"account_absent": true,
"allowed_email_domain": "example.test"
}
}
Mantenha cada adição de grupo como uma etapa, em vez de enviar uma lista a um endpoint amplo. Assim o executor pode aprovar, tentar de novo e compensar cada associação isoladamente. O plano também deixa a ordem visível. Se o fornecedor não atribui função antes do aceite, wait_for_acceptance é uma barreira real de estado, não uma espera temporizada.
Valide o plano com restrições locais antes de expor credenciais ou fazer chamadas. Confirme um identificador exato e conhecido para o tenant, normalize o domínio do email sem reescrever a parte local, resolva nomes de grupos para IDs imutáveis e recuse funções de proprietário ou administrador sem pedido explícito. Não deixe o agente descobrir tenants pesquisando todas as contas acessíveis a um token poderoso.
As leituras preliminares devem capturar o estado existente. Pesquise o sujeito pelo atributo único documentado e leia diretamente função e associações. Separe «não encontrado» de «falha de leitura». Um 403, timeout ou página truncada não prova ausência. Se a pesquisa usa consistência eventual ou paginação, registre a limitação e exija uma consulta mais forte antes da criação.
Congele o plano após a aprovação. Se o agente mudar email, função, ID de grupo ou opção de entrega, gere outro ID de operação e peça nova decisão. Caso contrário, a frase aprovada e as chamadas executadas podem divergir sem aviso.
Prepare o convite antes de conceder acesso
Crie ou envie primeiro o convite e pare até que o serviço demonstre o que aconteceu. Não inclua funções privilegiadas e grupos sensíveis só porque o endpoint aceita. O argumento comum a favor do pacote é eficiência: uma requisição parece atômica e envia uma notificação. Na prática, a maioria das APIs SaaS não promete transação entre identidade, função, notificação e propagação de grupos.
O endpoint de convite a organizações do GitHub mostra a tentação. A requisição pode trazer função e IDs de equipes. É conveniente no console, mas um executor autônomo perde pontos de verificação ao enviar tudo junto. Um erro pode rejeitar o conjunto, enquanto uma resposta perdida depois do processamento deixa dúvida sobre os efeitos. A pessoa ainda pode aceitar muito depois do fim da execução e ativar acesso naquele momento.
Prefira o convite com menor autoridade permitido. Se ele precisar de uma função, use membro comum e adie a elevação. Se exigir grupo ou canal, escolha uma área de chegada sem recursos sensíveis e adicione as associações finais depois que a identidade atingir o estado esperado. Algumas APIs impõem limites. O método Enterprise Grid do Slack, por exemplo, exige ao menos um canal. Isso pede um canal de chegada com pouco acesso, não todos os canais de trabalho na primeira chamada.
Registre se houve envio de email e capture ID do convite, estado, hora de criação e sujeito canônico retornado. Não guarde URL de aceite em um diário amplo, pois ela pode funcionar como capacidade ao portador. Se a API devolver uma para entrega separada, encaminhe-a pelo menor componente confiável e a oculte dos resultados vistos pelo agente.
O convite preparado precisa de condição terminal explícita. Use accepted, expired, cancelled e pending quando existirem. Caso contrário, derive o estado de campos documentados e identifique-o como derivado. Nunca interprete «o POST devolveu 201» como «a pessoa já acessa dados de produção».
Defina um prazo no registro local. Quando vencer, leia o estado e cancele um convite ainda pendente se o pedido já não for válido. Não programe cancelamento cego: a pessoa pode ter aceitado pouco antes do temporizador. Leia, compare e aja.
Cancelar convite é reversível apenas em sentido estreito. Pode impedir o aceite posterior, mas não recolhe email nem apaga o conhecimento da organização. Escreva esse limite no cartão de aprovação. «Reversível» deve descrever o estado de autorização remoto, não fingir que todas as consequências desaparecem.
Atribua funções quando a identidade estiver estável
Espere até vincular o pedido a um ID remoto estável. Emails ajudam na descoberta, mas são identificadores duradouros ruins: endereços mudam, aliases colidem e convidados podem aceitar com uma conta existente. O ID retornado pelo fornecedor deve ser o sujeito das chamadas seguintes.
Antes de alterar a função, leia a atual e a guarde como valor de compensação. Se o valor desejado já for igual, registre uma operação sem mudança em vez de escrever novamente. Esse registro prova que o executor verificou a condição e não tomou crédito por uma alteração de outro ator.
Trate elevação de modo diferente da associação comum. O agente pode atribuir função padrão com autorização de sessão, enquanto proprietário, administrador ou cobrança exigem aprovação por chamada. A fronteira segue a consequência do uso da credencial, não o método HTTP. PATCH /users/123 pode ser rotineiro ou desastroso conforme um campo.
Use comparação condicional quando a API permitir. ETag com If-Match, campo de versão ou revisão do fornecedor impede que o agente sobrescreva uma mudança humana posterior à leitura inicial. Diante de conflito, leia novamente e pare para revisão. Não force o plano antigo logo depois: a mudança concorrente pode ser justamente o fato que o operador precisa ver.
A entrada da função deve guardar valor anterior, solicitado e observado, ID remoto, estado da resposta e token de concorrência, mas nunca o token portador. Um registro mínimo é:
{
"operation_id": "prov_2026_07_24_0187",
"step": 3,
"action": "role.set",
"subject_id": "usr_1938",
"before": "guest",
"requested": "member",
"observed": "member",
"http_status": 200,
"undo": {"action": "role.set", "value": "guest"}
}
Não considere a função concluída só porque a atualização retornou sucesso. Leia o recurso e confirme o valor efetivo. APIs podem responder 202 Accepted, aplicar alterações de modo assíncrono ou separar registros pendentes e ativos. O diário deve mostrar requested até uma leitura provar observed.
Se o rebaixamento for a compensação, considere se ele também exige aprovação. Restaurar guest depois de proprietário acidental costuma ser mais seguro que esperar, mas a reversão automática pode conflitar com uma correção humana. Permita compensação automática apenas enquanto a versão armazenada corresponder à criada nesta operação. Caso contrário, pare e mostre a divergência.
Adicione cada grupo em sua própria chamada
Cada associação deve ter etapa, ID remoto, resultado e instrução de desfazer próprios. Grupos costumam esconder acesso amplo. engineering pode controlar repositórios, consoles de implantação, canais de incidentes e aplicativos concedidos por sincronização. O nome amigável não informa esse impacto.
Resolva grupos a partir de um catálogo permitido fora do prompt. Ele deve mapear nome visível para ID imutável do tenant e descrever associação direta, aninhada, dinâmica ou sincronizada. Dois grupos com o mesmo nome devem causar recusa. Se uma regra gerencia o grupo, não lute contra ela com escritas diretas repetidas.
A Directory API do Google Admin SDK torna o limite concreto: há endpoints separados para adicionar, atualizar e remover um membro por DELETE. A documentação também avisa que grupos aninhados podem demorar a aparecer e rejeita ciclos. Isso exige verificação do estado observado, não um laço que presume consistência imediata.
O SCIM traz outro comportamento útil. No exemplo PATCH da RFC 7644, adicionar um usuário já presente não deve mudar o recurso e deve retornar sucesso. Isso ajuda novas tentativas, mas não assuma que toda implementação segue o exemplo perfeitamente. Teste o fornecedor e mantenha a consulta de reconciliação.
Uma etapa de grupo distingue added, already_present, rejected e unknown. Already_present não cria ação de desfazer, pois a remoção apagaria acesso anterior à operação. Unknown significa que a escrita pode ter funcionado, mas a resposta ou verificação falhou. Exige reconciliação, não nova escrita otimista.
Processe grupos do menor para o maior privilégio. Adicione colaboração básica antes da administração de produção. Isso não elimina o dano de uma falha, mas deixa menos acesso quando a execução para. Peça aprovação nova ao cruzar qualquer grupo sensível, mesmo após grupos comuns terem funcionado.
Não paralelize as escritas apenas para reduzir latência. Chamadas paralelas embaralham provas, complicam limites de taxa e podem acionar sistemas seguintes em ordem imprevisível. Algumas chamadas sequenciais custam menos que investigar uma licença concedida antes do acordo de dados restritos.
Depois de cada adição, leia a associação direta, não uma visão achatada de acesso efetivo. O acesso pode vir de um grupo pai e permanecer depois da remoção da aresta direta. O recibo precisa nomear a aresta criada nesta operação.
Tentativas seguras começam pelo estado observado
Uma política de repetição não torna qualquer POST seguro. A RFC 9110 define PUT, DELETE e métodos seguros como idempotentes pelo efeito pretendido. Também diz para não repetir automaticamente uma requisição não idempotente sem saber que a semântica é idempotente ou que a original nunca foi aplicada. O código de provisionamento deve levar isso a sério.
O caso perigoso é um timeout depois do POST de convite. O servidor pode ter criado o convite e enviado email antes da falha da conexão. Repetir pode criar outro convite ou notificação. O próximo passo correto é ler por sujeito e tenant, adotar o objeto correspondente, repetir apenas após provar ausência ou parar quando o resultado for ambíguo.
Use chave de idempotência quando documentada. Derive-a do ID imutável da operação e do número da etapa, registre e reutilize na mesma tentativa lógica. Nunca gere outra porque houve timeout: uma chave nova informa ao servidor uma nova ação.
Sem chave, crie uma função de reconciliação para cada escrita antes de entregá-la ao agente. Ela precisa achar o objeto sem correspondência aproximada. Email e tenant identificam um convite; usuário e grupo identificam uma aresta. Se a API não oferece consulta exata, um resultado incerto exige confirmação humana.
Defina orçamento para repetição. Respeite Retry-After, aplique espera crescente e limitada a erros transitórios e pare em erros de validação, permissão ou conflito. Um 403 não é um 200 lento. Reapresentar a mesma negação também ensina operadores a aprovar sem ler.
Um executor confiável usa esta tabela:
| Resultado | Próxima ação |
|---|---|
| Sucesso certo e estado verificado | Confirmar o recibo da etapa |
| Falha certa sem mudança | Registrar e parar |
| Timeout depois do envio | Reconciliar antes de repetir |
| Resposta de sucesso com verificação diferente | Registrar divergência e parar |
| Limite de taxa com orientação | Esperar dentro do orçamento |
Separe tentativas de transporte das etapas lógicas. Cinco tentativas HTTP podem representar uma única associação. O diário principal mostra o resultado lógico e registros vinculados preservam códigos e tempos. Caso contrário, a auditoria pode confundir repetição com várias concessões.
Reversão é compensação, não viagem no tempo
Provisionamento SaaS raramente tem transação distribuída, portanto reverter significa compensar em ordem inversa. Remova associações criadas, restaure a função anterior e cancele o convite se ainda estiver pendente. Cada compensação é outra chamada real que pode falhar, exigir aprovação ou encontrar edição concorrente.
Monte a pilha a partir de mudanças confirmadas, não de passos planejados. Um grupo já presente não deve ser removido; uma função que nunca mudou não precisa ser restaurada. Se a verificação é desconhecida, reconcilie antes de decidir.
Um recibo útil guarda dados suficientes para tentar e limitar a operação inversa:
{
"action": "group.add",
"target": {"user_id": "usr_1938", "group_id": "grp_77"},
"result": "added",
"remote_version_after": "W/\"9012\"",
"compensation": {
"action": "group.remove",
"only_if_direct_membership_matches": true
}
}
A proteção de versão importa. Se o agente adiciona Priya e depois um gerente confirma a associação independentemente, uma reversão cega pode apagar uma decisão agora pertencente a outra pessoa. Quando o DELETE não expressa essa condição, leia a aresta e seus metadados, mostre o conflito e peça decisão humana.
Alguns efeitos não têm compensação. Email não volta, evento de auditoria não deve ser apagado, licença pode afetar cobrança e um provedor de identidade posterior pode propagar a mudança tarde. Marque esses efeitos residuais em vez de declarar reversão completa.
A reversão precisa de prazo e caminho de escalonamento. Credenciais expiram, serviços caem e o processo original termina. Persista recibos fora do contexto do agente para outro executor confiável continuar. O operador precisa ver rollback_pending, não uma falha discreta em uma conversa.
Teste a compensação em tenant não produtivo com comportamento real. Crie e cancele convite, verificando o link; adicione e remova associação direta, conferindo acesso efetivo; mude função de baixo risco e a restaure sob conflito. A documentação descreve intenção, esses testes mostram o comportamento real.
O diário deve provar causa e efeito
Um diário útil responde quem pediu a mudança, qual processo executou, qual fronteira de credencial autorizou, qual objeto remoto mudou e como o resultado foi verificado. Uma transcrição de ferramentas não basta. A narrativa do agente pode estar errada e corpos HTTP podem ser sensíveis ou grandes.
Dê IDs estáveis a operações e etapas. Registre hash do plano, tenant exato, sujeito normalizado, IDs remotos, aprovação, impressão da requisição, resposta, leitura de verificação e compensação. Guarde trechos editados apenas quando explicarem o resultado. Um hash da resposta permite comparação sem copiar dados pessoais.
Separe afirmações de observações. requested_role: member é intenção, response_status: 200 observa transporte e observed_role: member é estado remoto verificado. Um único booleano success destrói a evidência necessária em incidentes.
A visão de linha de comando deve tornar a conclusão parcial óbvia:
$ provision status prov_2026_07_24_0187
STEP ACTION RESULT UNDO
1 invitation.create accepted unavailable
2 acceptance.wait observed n/a
3 role.set changed ready: guest
4 group.add engineering added ready
5 group.add on-call denied none
STATE partial_failure
A saída informa que a conta existe, a função mudou, um grupo entrou e o último falhou. Não reduz a execução a «falha de provisionamento». A distinção orienta a compensação e a continuação após corrigir autorização.
Proteja o diário do agente que age. Se ele pode reescrever a própria prova, o registro vale pouco. Sallyport projeta sessões e chamadas individuais de um único log criptografado e encadeado por hash; sp audit verify verifica a cadeia offline sobre o texto cifrado sem chave. Isso não substitui logs do fornecedor, mas oferece uma sequência local independente.
Correlacione IDs locais com IDs de requisição do fornecedor. O suporte encontra rastros no servidor enquanto o registro local explica intenção e aprovação. Preserve horários, mas ordene por sequência local monotônica, pois relógios e eventos assíncronos podem divergir.
A retenção precisa de política clara. A evidência pode conter emails, grupos e histórico de funções. Guarde o mínimo necessário, criptografe, limite leitores e, quando permitido, expire dados auxiliares antes do registro principal.
A aprovação pertence aos limites de consequência
A aprovação humana funciona quando o cartão descreve uma consequência concreta. «Permitir provisionamento» é amplo demais. «Convidar [email protected] para acme-production sem enviar email» pode ser revisado. «Mudar usr_1938 de guest para member» e «adicionar usr_1938 a production-deployers» merecem decisões separadas quando os riscos diferem.
Mostre identificadores resolvidos e estado atual, não só as palavras do agente. O cartão deve exibir tenant, sujeito canônico, ação, antes e depois e compensação disponível. Para convite, diga se haverá email; para grupo, mostre o ID imutável junto do nome.
Autorização de sessão pode cobrir chamadas repetitivas de baixo risco, enquanto credenciais ou ações sensíveis exigem aprovação em cada uso. A escada fixa do Sallyport aplica essa separação: o cofre deve estar desbloqueado, um processo novo recebe autorização de sessão por padrão e uma marca por chave pode exigir aprovação por chamada. Coloque a credencial privilegiada no limite rigoroso, em vez de pedir ao modelo que se contenha.
Aprovação não corrige semântica fraca. Uma pessoa pode aprovar o grupo certo e sofrer POST duplicado após timeout. O executor continua responsável por idempotência, verificação e compensação. Um diário perfeito também não torna segura uma credencial poderosa demais.
Evite fadiga removendo avisos sem decisão. Leituras limitadas podem caber na sessão. Operações sem mudança devem ser registradas sem pedir aprovação para algo que não acontecerá. Agrupe associações idênticas de baixo risco apenas se a interface mostrar todos os alvos e o mecanismo guardar recibos separados.
A revogação deve parar passos futuros sem fingir que reverte os concluídos. Se o operador revoga a sessão após a etapa quatro, o executor cancela chamadas em fila, marca a operação interrompida e oferece a compensação. Não usa outra credencial nem abre nova sessão em segredo.
Uma execução falha deve continuar compreensível
Imagine um agente incluindo uma prestadora com conta comum e dois grupos. A leitura inicial não encontra conta. O convite dá timeout após envio, a reconciliação encontra um convite pendente e adota seu ID. A pessoa aceita, a função muda, o primeiro grupo entra e o segundo retorna 403 porque o token não tem autoridade.
Esse é um resultado parcial, não um erro indistinto. Existe uma conta ativa e uma associação direta. O agente deve parar, mostrar a etapa negada e oferecer duas opções: manter o estado confirmado enquanto se obtém autoridade para o grupo restante, ou compensar a primeira associação e restaurar a função antes de tratar a conta.
Ele não deve excluir o usuário. Isso pode remover dados, invalidar identidade já aceita ou colidir com sistemas posteriores. Também não deve repetir o 403, dizer que o email pode ser revertido nem substituir o grupo negado por outro mais amplo.
O diário permite continuação segura. Outro executor carrega plano e recibos imutáveis, lê o estado remoto e confirma conta, função e primeiro grupo. Se coincidirem, pede aprovação só para a associação restante. Se um gerente mudou a função, o plano ficou obsoleto e exige nova decisão.
O padrão vale além da entrada. A saída deve separar revogação de sessão, remoção de grupos, mudança de função, suspensão e exclusão, pois urgência e reversibilidade diferem. Licença também fica separada do grupo quando a API a expõe separadamente. A regra é manter no plano e na prova as fronteiras significativas do sistema remoto.
As chamadas extras são atrito deliberado. Elas criam pontos para verificar identidade, limitar autoridade, parar diante de divergência e desfazer apenas o que esta operação mudou. Um agente autônomo merece mais liberdade quando seu trabalho pode ser inspecionado nesses limites. Se a API força várias consequências em uma chamada irreversível, classifique-a com honestidade e coloque uma pessoa à frente.
FAQ
O agente deve criar o usuário SaaS e atribuir acesso numa chamada?
Em geral, não. Separe convite ou criação, função e cada grupo para verificar e desfazer de modo independente. Use endpoint combinado apenas quando os efeitos acoplados forem compreendidos e aprovados como unidade irreversível.
O que fazer quando a API de convite dá timeout?
Não repita o POST imediatamente. Procure o convite exato por tenant e sujeito, adote um resultado inequívoco e tente outra vez só após provar que a primeira requisição não mudou nada.
Excluir o usuário é uma reversão segura?
Raramente, pois pode remover dados e afetar uma identidade que já aceitou. Compense apenas mudanças confirmadas desta execução, como associações diretas e a função anterior.
Como tratar uma associação que já existia?
Registre already_present e não crie ação de desfazer. Removê-la durante a compensação apagaria acesso anterior à operação.
Quando uma ação é realmente reversível?
Quando o serviço permite restaurar o estado anterior observado e o executor tem identificadores suficientes para agir com segurança. Email, cobrança e propagação podem permanecer.
O que deve constar na auditoria?
IDs de operação e etapa, tenant, objetos remotos, valores anterior e solicitado, aprovação, resposta, verificação e compensação. Exclua credenciais e links de aceite.
O agente pode repetir PUT e DELETE automaticamente?
HTTP os define como idempotentes pelo efeito pretendido, mas a semântica do fornecedor e mudanças concorrentes ainda importam. Use condições de versão e verifique depois.
Grupos devem ser adicionados em paralelo?
Escritas sequenciais são mais seguras para acesso privilegiado. Preservam ordem, esclarecem a prova, simplificam limites e deixam um recibo preciso por associação.
O que um cartão de aprovação deve mostrar?
Uma consequência, tenant, sujeito canônico, ID imutável, estados anterior e posterior, efeitos de entrega e compensação. «Permitir entrada» esconde informação demais.
O que o agente faz depois de um 403?
Para e relata o estado parcial exato. Não repete o erro nem substitui por acesso mais amplo; espera autorização corrigida ou oferece compensação.