Bullgate Docs

Bullgate Access · documentação técnica

Resolução de identidade

Contrato da fase 1 para o caso em que uma pessoa comprova um telefone durante o cadastro, mas esse telefone verificado já pertence a outra identity no mesmo realm.

Implementado no código AccessFlow v1 Conflito de telefone Atualizado em 04/09/2026
A fase 1 move somente o telefone Não faz merge de identities, e-mails, senhas ou perfis do produto. Não cria e-mail alternativo e não recupera automaticamente a conta anterior.

Modelo mental

Quatro objetos, quatro donos diferentes

O conflito fica simples quando a integração não chama tudo de “conta”. A operação age sobre um identifier; as identities permanecem distintas.

currentIdentity

Identity da sessão Registration que está executando o flow. Recebe o telefone quando a transferência termina.

previousIdentity

Identity que já possui o telefone verificado. Mantém seu e-mail, credenciais, estado e referências existentes.

phone identifier

É o único objeto transferido. O mesmo registro passa a apontar para a identity atual; não nasce uma cópia concorrente.

Perfil do produto

Pertence ao sistema integrador. O Bullgate não mistura dados, histórico ou cadastros locais das duas identities.

Quando o caso nasce

O conflito só aparece depois da prova do telefone

O código consulta o dono do telefone verificado depois que o OTP foi aprovado. Conhecer ou digitar um número não abre autoridade de resolução.

Dono encontradoComportamentoResultado
NenhumCria o identifier verificado para a identity atual.phoneVerified
A própria identityNão existe conflito entre identities.phoneVerified
Outra identityPersiste PhonePossession, cria um IdentityResolutionCase e devolve o step de conflito.resolvePhoneConflict
Telefone sem verificação segue outro contrato Se a policy permite coletar telefone sem OTP, um número já usado retorna feedback phone-already-in-use. A resolução descrita nesta página não é aberta nesse caminho.

Fluxo executado

Da coleta ao resultado terminal

Coletar

A UI envia requestPhoneVerification com o telefone em E.164.

Comprovar

A pessoa confirma o OTP. O sucesso registra a prova PhonePossession.

Detectar

O Access encontra outra identity como dona verificada do mesmo identifier.

Apresentar

O flow devolve resolvePhoneConflict, dica mascarada e somente as ações permitidas.

Autorizar

transferPhoneToCurrentIdentity recebe o e-mail anterior e registra sucesso ou tentativa.

Executar

Prova, grant, transferência, sessão Product e conclusão são gravados em uma transação.

Entrada da interface

Snapshot de conflito

A interface não deduz possibilidades pelo nome da tela. Ela renderiza o step e oferece apenas os itens presentes em flow.actions.

Exemplo quando telefone é opcional

{
  "protocolVersion": 1,
  "flowId": "0199…",
  "revision": 4,
  "intent": "continueRegistration",
  "status": "active",
  "expiresAt": "2026-09-04T15:30:00Z",
  "step": {
    "type": "resolvePhoneConflict",
    "previousEmailHint": "m***@empresa.com"
  },
  "actions": [
    { "id": "0199…", "type": "transferPhoneToCurrentIdentity" },
    { "id": "0199…", "type": "skipRegistration" }
  ]
}
previousEmailHintDica mascarada para reconhecimento humano. Nunca use como valor de envio, autocomplete ou prova.
actionsAutoridade da revisão atual. Se a policy exige telefone, skipRegistration não aparece.
revisionDeve voltar como expectedRevision. Qualquer snapshot mais novo invalida a ação antiga.
expiresAtExpiração absoluta do flow. Não é a duração do grant nem do OTP já consumido.

Frontend e BFF

Executar a transferência

Com o SDK React Native, passe o snapshot recebido, o tipo oferecido e apenas o e-mail informado pela pessoa. O SDK mantém IDs e revisão do contrato.

React Native

if (flow.step?.type === "resolvePhoneConflict") {
  const canTransfer = flow.actions.some(
    action => action.type === "transferPhoneToCurrentIdentity",
  );

  if (canTransfer) {
    current = await access.actOnFlow(
      flow,
      "transferPhoneToCurrentIdentity",
      { previousEmail },
    );
  }
}

HTTP no BFF

POST /bullgate/access/v1/flows/{flowId}/actions

{
  "requestId": "0199…",
  "expectedRevision": 4,
  "action": {
    "id": "0199…",
    "type": "transferPhoneToCurrentIdentity",
    "input": { "previousEmail": "[email protected]" }
  }
}
Não invente o ID da ação action.id e action.type precisam ser copiados do mesmo item oferecido no snapshot. O e-mail é normalizado pelo Access antes da comparação.

Saída de sucesso

Telefone transferido, sessão promovida

O envelope terminal traz o resultado do flow e uma sessão autenticada para a identity atual.

{
  "flow": {
    "status": "completed",
    "step": null,
    "actions": [],
    "result": {
      "type": "completed",
      "outcome": "phoneTransferred",
      "previousIdentityId": "0198…",
      "currentIdentityId": "0199…"
    }
  },
  "session": {
    "state": "authenticated",
    "identityId": "0199…",
    "application": { "id": "…" }
  }
}

O que muda

  • O telefone passa para currentIdentityId.
  • O caso termina como Resolved.
  • A sessão Registration é revogada.
  • Uma sessão Product é emitida para a identity atual.

O que não muda

  • O e-mail anterior continua na identity anterior.
  • Não há cópia de senha ou provider social.
  • As identities não são consolidadas.
  • O perfil local anterior não é movido.

Resposta recuperável e falha HTTP

Trate feedback e conflito de protocolo de formas diferentes

CódigoOnde apareceResposta da integração
phone-conflict-email-mismatchflow.feedbackMarque previousEmail, mantenha o flow e permita nova tentativa se a ação continuar presente.
phone-conflict-too-many-attemptsflow.feedbackA transferência desaparece. Se telefone for opcional, ainda pode existir skipRegistration.
revision-conflictHTTP 409Busque o snapshot atual com getFlow e renderize novamente. Não repita a ação antiga.
action-not-availableHTTP 409A ação não pertence mais ao estado atual. Recarregue o flow.
request-id-conflictHTTP 409O mesmo requestId foi usado com payload diferente. Gere nova chave somente para uma nova intenção.
flow-not-foundHTTP 404Capability ausente, inválida ou fora do escopo. Não tente reconstruí-la no cliente.
session-inactiveHTTP 401A sessão Registration de origem não está mais ativa; reinicie pela autenticação.

Regra implementada

Duas provas autorizam a transferência atual

A fase 1 usa uma regra fixa no fluxo. Ela ainda não expõe um objeto configurável de ResolutionPolicy.

PhonePossession

Nasce da confirmação bem-sucedida do OTP enviado ao telefone em conflito e fica vinculada ao mesmo contexto de cadastro.

PreviousEmailKnowledge

Nasce quando o e-mail normalizado informado coincide com o identifier da identity anterior. Não é um código enviado ao e-mail.

MaxAttemptsValor padrão atual: 5. Tentativas de e-mail anterior são registradas individualmente.
ResolutionGrantLifetimeMinutesValor padrão atual: 15. Neste caminho, o grant é criado e consumido dentro da mesma transação; não é entregue ao aplicativo.
EscopoCaso, provas e identifier precisam pertencer ao mesmo realm, environment, registration context e par de identities.

Máquina de estados persistida

Do caso aberto à conclusão

No sucesso atual, as três últimas transições acontecem na mesma transação. Elas continuam explícitas para proteger invariantes e permitir evolução do modelo.

CollectingProofsCaso aberto, recebendo escolha e provas.
ReadyOutcome escolhido e requisitos satisfeitos.
ExecutingGrant ativo e mutação em execução.
ResolvedTelefone transferido e conclusão persistida.
Estados reservados pelo domínio Rejected, Expired e Cancelled existem no enum, mas o fluxo de transferência da fase 1 não oferece comandos públicos específicos para conduzir o caso a esses estados.

Consistência

A decisão é revalidada na hora de gravar

O Access não confia apenas no snapshot mostrado na tela. Antes do commit, confere novamente ownership, provas, tentativas, estado do caso e finalidade da sessão.

  • O caso ainda precisa estar em CollectingProofs e sem outcome escolhido.
  • O número de tentativas precisa coincidir com o valor esperado e estar abaixo do limite.
  • O e-mail anterior precisa continuar pertencendo à identity anterior.
  • O telefone precisa continuar verificado e pertencendo à identity anterior.
  • A prova de posse do telefone precisa existir no mesmo contexto.
  • Proof, attempt, grant e sessão precisam apontar para o caso e a identity corretos.
Um único commit Escolher o outcome, marcar o caso como pronto, consumir o grant, transferir o telefone, resolver o caso, concluir o registration context, emitir a sessão Product, revogar sessões Registration e registrar a revisão acontecem juntos.
Ownership mudou durante a interação Se outra operação mover o telefone antes do commit, o Access responde como revision-conflict. A integração deve recarregar o snapshot; nunca forçar o resultado antigo.

Comportamento da aplicação

O que a UI precisa fazer — e o que não pode prometer

Deve

  • Renderizar pelo discriminante step.type.
  • Exibir a dica mascarada sem transformá-la em dado editável.
  • Executar somente ações presentes em flow.actions.
  • Enviar a revisão exibida e tratar feedback no campo correto.
  • Atualizar a sessão local quando o envelope vier autenticado.

Não deve

  • Dizer que as contas serão unidas ou recuperadas.
  • Mostrar “continuar sem telefone” se skipRegistration não foi oferecida.
  • Repetir automaticamente uma ação após conflito de revisão.
  • Usar e-mail ou telefone como chave do perfil local.
  • Apagar sessão por falha operacional transitória.

Contrato honesto

Modelado não significa disponível

O SDK e o domínio já reservam nomes para uma resolução mais ampla. Integrações da fase 1 devem ignorá-los até que apareçam no array de ações de um snapshot real.

Disponível agora

  • Detectar telefone verificado em outra identity.
  • Provar posse por OTP.
  • Comprovar conhecimento do e-mail anterior.
  • Transferir somente o telefone.
  • Continuar sem telefone quando a policy permitir.

Não disponível na fase 1

  • recoverPreviousIdentity.
  • ConsolidateProvisionalIdentity.
  • Prova de posse do e-mail anterior.
  • Reset de senha dentro desse flow.
  • Merge automático ou e-mail alternativo.
  • Decisão manual de operador.
Regra para compatibilidade futura Tipos reservados não autorizam chamadas. Uma capacidade passa a existir para o cliente somente quando o servidor a oferece em flow.actions naquela revisão.
Chat Bullgate
Assistente Bullgateprévia

Olá! O assistente ainda está sendo preparado. Enquanto isso, fale comigo:

Marco · Bullgate

Prévia: nenhuma mensagem é enviada por este campo.