Descrição do problema
A mesma identidade do Discord é criada e atualizada por três fluxos que salvam os dados do usuário em formatos diferentes.
- O OAuth salva
username, email e avatar na raiz de metadata
- A importação de mensagens salva o usuário em
metadata.author
- A importação de perfil salva o dump em
metadata.user
Os fluxos não apenas usam formatos diferentes. O OAuth e a importação de perfil usam updateOrCreate e substituem o metadata inteiro. Com isso, o último fluxo executado apaga informações salvas pelo anterior.
A importação de perfil também grava um ClientAccessManager vazio em credentials e substitui connected_at pela data em que a pessoa entrou no servidor. Esses campos pertencem à conexão OAuth e não deveriam ser atualizados por uma importação de dados públicos do perfil.
Comportamento esperado
Os dados principais do usuário do Discord devem possuir um formato canônico independente da origem.
Cada fluxo deve atualizar somente os campos pelos quais é responsável. A importação de perfil ou mensagem não deve apagar credenciais, email ou informações da conexão OAuth. Um novo login OAuth também não deve descartar o payload útil importado do perfil.
A identidade deve continuar ligada ao mesmo usuário durante qualquer atualização, seguindo a resolução por provider e external_account_id já adotada no PR #207.
Comportamento atual
Foi possível reproduzir o problema localmente sem conta real e sem acessar o Discord.
No cenário OAuth seguido de importação de perfil, a importação remove email e avatar da raiz do metadata, substitui o payload pelo formato com metadata.user, apaga access token e refresh token e troca connected_at pela data de entrada no servidor.
No cenário importação de perfil seguida de OAuth, o OAuth substitui o payload completo e remove campos como user, badges e guild_member, deixando apenas email, avatar e username na raiz.
A reprodução automatizada executou 2 testes com 15 assertions e confirmou a sobrescrita nos dois sentidos.
Passos para reproduzir
- Criar localmente um usuário e uma
ExternalIdentity do Discord com metadata e credenciais equivalentes ao resultado do OAuth
- Executar
ImportDiscordProfileAction para o mesmo provider e external_account_id
- Atualizar o model e conferir que os campos do OAuth foram substituídos
- Repetir na ordem inversa, criando a identidade pela importação de perfil
- Executar
AttachProviderToUser com DTOs locais de OAuth
- Conferir que o payload importado do perfil foi substituído pelo recorte do OAuth
Ambiente
- Sistema operacional: Windows
- Navegador / versão: não se aplica
- Ambiente: local
- Branch:
4.x
- Commit validado:
021bc456
- Banco: PostgreSQL local em Docker
- Reprodução: Pest com factories e DTOs locais, sem conta real e sem chamadas externas
Sugestão de correção (opcional)
Criar um normalizador único para os dados públicos da identidade do Discord dentro de integration-discord. Os três produtores devem projetar username, global_name, avatar e email para o mesmo contrato canônico. Payloads brutos que ainda forem necessários podem ficar separados por origem, sem obrigar os consumidores a descobrir se o usuário está em user, author ou na raiz.
Definir responsabilidade por campo antes de alterar as Actions.
- O fluxo OAuth é responsável por
credentials, connected_at, connected_by, disconnected_at e pelos dados recebidos na autenticação
- A importação de perfil é responsável pelos dados públicos e pelo snapshot do perfil do Discord
- A importação de mensagem pode criar uma identidade ausente com os dados públicos disponíveis, mas não deve substituir credenciais ou dados mais completos de uma identidade existente
guild_member.joined_at deve continuar como dado de participação no servidor e não substituir o horário de conexão OAuth
Substituir as atualizações amplas por merge controlado ou por uma Action dedicada que atualize somente os campos pertencentes a cada origem. A resolução deve continuar identity first e nunca trocar model_id de uma identidade existente, seguindo a correção feita no PR #207.
Adicionar testes para as ordens OAuth seguido de perfil, perfil seguido de OAuth, mensagem seguida de perfil e identidade já existente. Os testes devem validar metadata canônico, preservação e leitura das credenciais, datas da conexão e manutenção do mesmo usuário.
Durante a transição, manter a compatibilidade adicionada no PR #478. Para os registros antigos, avaliar um backfill em chunks com dry-run, contadores e nenhuma alteração de model_id, seguindo o cuidado usado nas ferramentas de recuperação do PR #207.
Contexto adicional
O problema apareceu durante a revisão do PR #478, que já foi mergeado. Esse PR corrigiu o Meeting Showcase para ler os três formatos existentes, mas não alterou a origem da inconsistência.
Os padrões considerados para esta proposta foram o contrato de metadata e credenciais introduzido no PR #186, a resolução identity first e recuperação segura do PR #207, o fluxo de reconexão OAuth do PR #290 e a compatibilidade temporária do consumidor criada no PR #478.
Pontos envolvidos no código:
app-modules/identity/src/Auth/Actions/AttachProviderToUser.php
app-modules/integration-discord/src/ETL/Actions/ImportDiscordProfileAction.php
app-modules/integration-discord/src/ETL/Actions/ImportDiscordMessageAction.php
Descrição do problema
A mesma identidade do Discord é criada e atualizada por três fluxos que salvam os dados do usuário em formatos diferentes.
username,emaileavatarna raiz demetadatametadata.authormetadata.userOs fluxos não apenas usam formatos diferentes. O OAuth e a importação de perfil usam
updateOrCreatee substituem ometadatainteiro. Com isso, o último fluxo executado apaga informações salvas pelo anterior.A importação de perfil também grava um
ClientAccessManagervazio emcredentialse substituiconnected_atpela data em que a pessoa entrou no servidor. Esses campos pertencem à conexão OAuth e não deveriam ser atualizados por uma importação de dados públicos do perfil.Comportamento esperado
Os dados principais do usuário do Discord devem possuir um formato canônico independente da origem.
Cada fluxo deve atualizar somente os campos pelos quais é responsável. A importação de perfil ou mensagem não deve apagar credenciais, email ou informações da conexão OAuth. Um novo login OAuth também não deve descartar o payload útil importado do perfil.
A identidade deve continuar ligada ao mesmo usuário durante qualquer atualização, seguindo a resolução por
providereexternal_account_idjá adotada no PR #207.Comportamento atual
Foi possível reproduzir o problema localmente sem conta real e sem acessar o Discord.
No cenário OAuth seguido de importação de perfil, a importação remove
emaileavatarda raiz do metadata, substitui o payload pelo formato commetadata.user, apaga access token e refresh token e trocaconnected_atpela data de entrada no servidor.No cenário importação de perfil seguida de OAuth, o OAuth substitui o payload completo e remove campos como
user,badgeseguild_member, deixando apenasemail,avatareusernamena raiz.A reprodução automatizada executou 2 testes com 15 assertions e confirmou a sobrescrita nos dois sentidos.
Passos para reproduzir
ExternalIdentitydo Discord com metadata e credenciais equivalentes ao resultado do OAuthImportDiscordProfileActionpara o mesmoprovidereexternal_account_idAttachProviderToUsercom DTOs locais de OAuthAmbiente
4.x021bc456Sugestão de correção (opcional)
Criar um normalizador único para os dados públicos da identidade do Discord dentro de
integration-discord. Os três produtores devem projetarusername,global_name,avatareemailpara o mesmo contrato canônico. Payloads brutos que ainda forem necessários podem ficar separados por origem, sem obrigar os consumidores a descobrir se o usuário está emuser,authorou na raiz.Definir responsabilidade por campo antes de alterar as Actions.
credentials,connected_at,connected_by,disconnected_ate pelos dados recebidos na autenticaçãoguild_member.joined_atdeve continuar como dado de participação no servidor e não substituir o horário de conexão OAuthSubstituir as atualizações amplas por merge controlado ou por uma Action dedicada que atualize somente os campos pertencentes a cada origem. A resolução deve continuar identity first e nunca trocar
model_idde uma identidade existente, seguindo a correção feita no PR #207.Adicionar testes para as ordens OAuth seguido de perfil, perfil seguido de OAuth, mensagem seguida de perfil e identidade já existente. Os testes devem validar metadata canônico, preservação e leitura das credenciais, datas da conexão e manutenção do mesmo usuário.
Durante a transição, manter a compatibilidade adicionada no PR #478. Para os registros antigos, avaliar um backfill em chunks com
dry-run, contadores e nenhuma alteração demodel_id, seguindo o cuidado usado nas ferramentas de recuperação do PR #207.Contexto adicional
O problema apareceu durante a revisão do PR #478, que já foi mergeado. Esse PR corrigiu o Meeting Showcase para ler os três formatos existentes, mas não alterou a origem da inconsistência.
Os padrões considerados para esta proposta foram o contrato de metadata e credenciais introduzido no PR #186, a resolução identity first e recuperação segura do PR #207, o fluxo de reconexão OAuth do PR #290 e a compatibilidade temporária do consumidor criada no PR #478.
Pontos envolvidos no código:
app-modules/identity/src/Auth/Actions/AttachProviderToUser.phpapp-modules/integration-discord/src/ETL/Actions/ImportDiscordProfileAction.phpapp-modules/integration-discord/src/ETL/Actions/ImportDiscordMessageAction.php