Diagnóstico de problemas
Um guia prático dos modos de falha mais comuns na operação. Cada entrada traz o sintoma, a causa raiz mais provável e os comandos para diagnosticar.
Se o bootstrap falhar, o banner de resumo de prontidão não é impresso — o serviço encerra antes de chegar a ele. Procure a exceção de bootstrap no local de log de cada alvo:
| Alvo | Onde procurar |
|---|---|
| Linux | journalctl -u bulksigner -n 200 |
| Windows | Visualizador de Eventos → Logs do Windows → Aplicativo (origem Lacuna.BulkSigner) — as exceções de bootstrap são registradas ali antes de o destino de log em arquivo estar configurado |
| Docker | docker compose logs bulksigner --tail=200 |
| Console | A saída do terminal |
O serviço não inicia
Os perfis de assinatura ficam no banco de dados operacional, e um certificado com problema é corrigido na
própria página do perfil no dashboard. Um host que se recusasse a iniciar não conseguiria servir essa
página; por isso, um perfil cujo certificado não pode ser aberto — um arquivo ausente, uma senha errada, um
thumbprint que não corresponde a nada, um Key Vault inacessível ou sem autorização, um blob ilegível —
agora aparece como DEGRADED e o host inicia. Só esse perfil fica sem assinar; os jobs dele falham
com profile.degraded (veja Um job falha com profile.degraded),
e todos os outros perfis continuam funcionando.
O que ainda impede o boot é a validação da configuração: as regras abaixo, verificadas no arquivo de
configuração ou no ambiente — a licença do PKI, o formato do bloco Blob, um PIN de PKCS#11 gravado em
arquivo, uma origem de repositório do Windows em um host não Windows e as verificações de uma seção
Signing:Profiles[] importada em um primeiro boot. As mesmas regras recusam um salvamento na página do
perfil.
Signing:PkiSdkLicense is required
Sintoma. O bootstrap lança uma exceção de validação sobre Signing:PkiSdkLicense.
Causa raiz. Nem Signing__PkiSdkLicense (ambiente) nem Signing:PkiSdkLicense (configuração)
tem um valor preenchido.
Correção. Defina a variável de ambiente no alvo de instalação:
| Alvo | Comando |
|---|---|
| Linux | Acrescente Signing__PkiSdkLicense=<base64> a /etc/bulksigner/bulksigner.env, então sudo systemctl restart bulksigner. |
| Windows | [Environment]::SetEnvironmentVariable("Signing__PkiSdkLicense", "<base64>", "Machine"); Restart-Service LacunaBulkSigner |
| Docker | Acrescente Signing__PkiSdkLicense=<base64> a deploy/docker/.env, então docker compose up -d. |
Signing:TrustLacunaTestRoot is true while the environment is 'Production'
Sintoma. O boot é recusado com essa frase, que termina em "unset the key, or run the homologation host as Staging (ASPNETCORE_ENVIRONMENT=Staging)".
Causa raiz. O host foi instruído a confiar na raiz PKI de teste da Lacuna — a emissora dos
certificados de teste Turing / Fermat (veja Certificados) — e, ao mesmo tempo, se declara
de produção. A combinação é recusada de propósito: um host de produção nunca confia em uma raiz que não
é uma autoridade certificadora. No Azure App Service, o nome do ambiente é Production por padrão quando
nada o define; por isso, um host de homologação que copiou um bloco de configurações de produção e
acrescentou a chave cai neste caso.
Correção. Uma de duas, conforme o papel do host:
- Host de produção: remova
Signing__TrustLacunaTestRoot(ou defina-a comofalse). Certificados reais não precisam de nada. - Host de homologação: defina
ASPNETCORE_ENVIRONMENT=Stagingjunto com a chave. Qualquer nome diferente deProductioné aceito; as linhasenvironmentetrust setdo banner passam a mostrar o papel do host.
Auth:ApiKey is required
Sintoma. O bootstrap lança um erro sobre Auth:ApiKey.
Causa raiz. Não há valor, ou o valor tem menos que o mínimo de 16 caracteres.
Correção. Gere uma chave forte (veja Segurança) e defina a variável de ambiente correspondente.
Imagens anteriores à 2.3.1 traziam uma chave de API provisória no próprio arquivo de configurações
padrão. Uma implantação que nunca definiu Auth:ApiKey estava rodando, sem aviso, com esse valor
provisório e, após a atualização, se recusa a iniciar, indicando a chave. Defina Auth__ApiKey como
documentado em todos os caminhos de instalação.
Pkcs11 PIN env var <nome> is empty
Sintoma. O serviço inicia, mas o perfil aparece como DEGRADED — no banner de inicialização, em
/profiles e em /api/ready/details — com o motivo
PKCS#11 PIN environment variable '<nome>' is empty. Antes da 2.1.0, isso impedia o boot.
Causa raiz. Signing:Certificate:Source = Pkcs11, mas a variável de ambiente configurada não está
definida ou está vazia.
Correção. Defina a variável de ambiente indicada em Signing:Certificate:Pkcs11:PinEnvVar (padrão
BULK_SIGNER_PKCS11_PIN) e reinicie. Veja Certificados.
WindowsStore source is not supported on this OS
Sintoma. O bootstrap falha imediatamente em um host Linux ou Docker.
Causa raiz. Signing:Certificate:Source = WindowsStore configurado em um host não Windows.
Correção. Troque a origem. No Linux / Docker, use Pfx, Pkcs11 ou AzureKeyVault.
AzureKeyVault:Endpoint must be an absolute https:// URL
Sintoma. O bootstrap falha ao validar o bloco do Azure Key Vault.
Causa raiz. Signing:Certificate:AzureKeyVault:Endpoint recebeu apenas um nome DNS
(my-vault.vault.azure.net) ou uma URL http://. O conector precisa da URL completa do cofre; sem esta
verificação, um nome sem esquema falharia no interior do cliente do Azure, com uma mensagem bem menos útil.
Correção. Use a URL do cofre exatamente como o portal do Azure a mostra, por exemplo
https://my-vault.vault.azure.net/.
Certificate '<caminho>' does not match Azure Key Vault key '<nome>'
Sintoma. O banner de inicialização mostra o perfil como DEGRADED, com um motivo dizendo que a
chave pública do certificado difere da chave do cofre, e os jobs do perfil falham com profile.degraded.
(Antes da 2.1.0, isso impedia o boot.)
Causa raiz. CerPath — ou Blob:Url, quando o certificado é lido
de um blob — e KeyName se referem a pares de chaves
diferentes. Em geral, o certificado foi renovado com uma nova chave do cofre enquanto o KeyName
ainda aponta para a antiga, ou, após uma edição, o local do certificado ficou apontando para um certificado
sem relação com a chave. A mensagem indica o local que o perfil usa, como file '<caminho>' ou
blob '<conta>/<container>/<blob>'.
Esta verificação existe porque a alternativa é pior: sem ela, o perfil assinaria normalmente e produziria
assinaturas que nenhum verificador aceita, e a falha só apareceria job a job — e apenas em perfis com
Verify = true.
Correção. Descubra qual lado está desatualizado comparando diretamente as duas chaves públicas:
openssl x509 -in signer.cer -noout -pubkey
az keyvault key download --vault-name my-vault --name bulk-signer-signing-key --encoding PEM --file -
Os dois blocos PEM precisam ser idênticos byte a byte. Depois, corrija o lado errado — o local do certificado ou o nome da chave são corrigidos na página do perfil, em Editar certificado — e reinicie.
Falha de autenticação ou autorização no Azure Key Vault na inicialização
Sintoma. O banner de inicialização mostra o perfil como DEGRADED ao carregar o certificado, com um
erro do Azure como AADSTS7000215 (client secret inválido), AADSTS700016 (aplicativo não encontrado) ou
um Forbidden na operação de chave. O host inicia; os jobs do perfil falham com profile.degraded. (Antes
da 2.1.0, isso impedia o boot.)
Causas possíveis.
- Client secret expirado. Os segredos do Entra ID têm validade limitada; a expiração aparece como uma
falha repentina de boot depois de uma reinicialização que antes funcionava. Gere um novo segredo no Azure
e atualize
Signing__Certificate__AzureKeyVault__AppSecret. AppIderrado, ou o registro de aplicativo está em um tenant diferente do cofre.- Permissões de chave ausentes. O registro de aplicativo precisa de get na chave e da operação
criptográfica sign — a role interna Key Vault Crypto User em um cofre com RBAC. Um
Forbiddencom credenciais que, fora isso, são válidas aponta para esta causa. - Sem rota de rede. O host precisa acessar
*.vault.azure.netelogin.microsoftonline.com. Confira as regras de saída e a configuração de proxy.
As falhas são informadas por perfil, e todos os perfis com falha aparecem no mesmo banner; assim, uma implantação com vários perfis vê todos os perfis mal configurados em um único boot, em vez de um a cada reinicialização. Depois de corrigir o segredo ou a atribuição de role, reinicie — o certificado é aberto uma única vez, na inicialização.
Um blob de material de assinatura não pode ser lido na inicialização
Sintoma. O banner de inicialização mostra um perfil como DEGRADED, citando um blob no formato
<conta>/<container>/<blob>. O host inicia. O perfil não consegue assinar até que o blob esteja
legível e o serviço seja reiniciado; os demais perfis não são afetados, e os jobs roteados para este falham
com profile.degraded.
Antes da 2.1.0, um blob ilegível impedia o boot. Agora ele degrada apenas esse perfil, pelo motivo explicado no início desta seção.
São três mensagens distintas, porque cada uma tem uma correção diferente:
| Mensagem | Causa raiz | Correção |
|---|---|---|
… does not exist | O nome do container ou do blob está errado. Ambos diferenciam maiúsculas de minúsculas, e a URL é lida exatamente como foi escrita. | Corrija Blob:Url. Confirme com az storage blob exists --account-name <a> --container-name <c> --name <b>. |
… credential was refused (HTTP 403) | A credencial foi autenticada, mas não tem permissão para ler o blob. | Para ManagedIdentity / ServicePrincipal, conceda Storage Blob Data Reader no container ou na conta — nunca é necessário nada mais amplo. Para AccountKey, a chave está errada ou foi rotacionada. |
… the <modo> credential could not be obtained | Não foi possível obter a credencial, antes mesmo de qualquer requisição. | ManagedIdentity: o host precisa de uma identidade atribuída pelo sistema, e um host fora do Azure não tem nenhuma. ServicePrincipal: confira TenantId / AppId / AppSecret — um segredo expirado produz exatamente a mesma mensagem. |
Qualquer outro erro (um 5xx, uma falha de transporte) é informado com o status que o serviço retornou e indica um problema de conectividade: o host precisa de HTTPS de saída para o endpoint de blob. A leitura já passou por três novas tentativas com backoff exponencial, então uma falha momentânea não chega a gerar esta mensagem.
A renovação não se resolve sozinha. O blob é lido uma única vez, no boot; por isso, substituir o conteúdo dele exige uma reinicialização, exatamente como substituir um arquivo local — e corrigir o blob não tira do estado degradado um host em execução.
O logotipo do cliente não aparece nas páginas de login ou de aprovação
O serviço está no ar, mas as páginas mostram apenas a marca do produto. Isso é intencional: um logotipo
configurado (Branding:CustomerLogo, veja Configuração) cujos bytes não puderam ser usados
não impede o serviço de iniciar. O motivo aparece em três lugares:
- na linha
customer logodo banner de resumo de prontidão —not loaded from file '…': <motivo>; - no log de inicialização, em um Warning com o texto
Customer logo not loaded from …; - na página Sistema, em um alerta no topo do painel de armazenamento.
O motivo é um destes: o arquivo ou blob está ausente ou não pode ser lido (confira o caminho, a montagem
no Docker, a atribuição de role do blob); o arquivo está vazio ou tem mais de 256 KiB (exporte um
menor — ele é exibido com no máximo 80 px de altura); ou os bytes não correspondem à extensão (um JPEG
renomeado para .png, uma página HTML salva como .svg — renomeie ou exporte de novo). Corrija e
reinicie: o logotipo é lido uma única vez, no boot.
Se o serviço não iniciou, a mensagem cita Branding:CustomerLogo:… e uma das regras de formato: Path
e Blob definidos ao mesmo tempo, uma extensão fora de .png / .jpg / .jpeg / .webp / .svg, ou
um bloco de blob sem Url ou sem Credential. Esses casos são recusados no boot, como qualquer outro
erro de configuração.
A inicialização é recusada porque Signing:ProfileSecretsKey não está definida
Sintoma. Uma de duas recusas — e qual delas aparece indica em que ponto a implantação está:
Refusing to start: this deployment's operational store holds signing profile secrets that were
encrypted under Signing:ProfileSecretsKey, and that key is not set. …
Refusing to start: signing profiles are being imported into the operational store for the first
time, and some of them carry a secret — 'folha', 'nfe' — while Signing:ProfileSecretsKey is not set. …
A primeira vem de um host que já guarda dados de perfil criptografados. A segunda é o primeiro boot após uma atualização, recusando no exato momento em que esses dados passariam a existir — de modo que o banco nunca chega a guardar um valor que nada consegue abrir. A segunda cita os perfis.
Causa raiz. Os perfis de assinatura são linhas no banco de dados operacional, e uma senha de PKCS#12, um segredo de aplicativo do Key Vault, uma credencial de blob de material de assinatura e bytes de PKCS#12 enviados por upload são criptografados em repouso com uma chave mantida fora do banco de dados. A recusa só acontece com o par — dados criptografados e nada com que descriptografá-los —, nunca com uma das metades sozinha. Uma implantação cujos perfis não têm segredo nunca precisa fornecer uma chave, e é por isso que a maioria das instalações atualiza sem jamais passar por isto.
Por que este caso recusa o boot, se um certificado com problema apenas degrada o perfil. A correção aqui é uma variável de ambiente, e não algo em uma página do dashboard; recusar, portanto, não cria um impasse — e uma chave ausente desativaria de uma só vez todos os perfis com segredo, resultando em um host que se declara saudável sem conseguir assinar para ninguém.
Correção. Defina a chave e inicie de novo:
Signing__ProfileSecretsKey='<o valor sob o qual os segredos foram salvos>'
Ela precisa ter o mesmo valor com o qual os segredos foram salvos — se você não o tem mais, veja Um perfil está degradado dizendo que um segredo armazenado não pôde ser descriptografado. Mantenha-a fora do controle de versão, fora do backup que contém o banco de dados e no mesmo lugar em que ficam os outros segredos irrecuperáveis (veja Segurança).
Se a segunda recusa cita um perfil que você nunca declarou — ou cita os seus junto com um que você não
declarou —, o host está lendo um array Signing:Profiles[] de um arquivo de configurações abaixo do seu.
A configuração mescla arrays por índice e consegue sobrescrever uma chave, mas nunca removê-la: as suas
configurações Signing__Profiles__0__* são mescladas sobre o que aquele arquivo declarar no índice 0 e
herdam todas as chaves que não definiram, inclusive uma senha de PFX. Imagens anteriores à 2.3.1 traziam,
dessa forma, perfis de exemplo de desenvolvimento no próprio arquivo de configurações padrão. Atualize a
imagem ou remova o arquivo que os declara. Não resolva definindo a chave: a importação acontece uma
única vez e nada exclui um perfil, então isso importaria os perfis indevidos para sempre. Nada foi
gravado — a recusa ocorre antes da importação —, então o próximo boot importa de forma limpa.
Um aviso relacionado que não é esta recusa. Se o banco operacional não respondeu na inicialização e a chave não está definida, o host inicia e avisa que não conseguiu verificar se existem segredos de perfil e que o próximo boot que conseguir acessar o banco pode ser recusado. Trate isso como um lembrete para definir a chave antes de o banco voltar.
Encryption.Salt must decode to at least 16 bytes
Sintoma. O bootstrap falha quando Encryption:Enabled = true.
Causa raiz. O salt em base64 configurado está ausente, malformado ou tem menos de 16 bytes depois de decodificado.
Correção. Gere um novo com 32 bytes aleatórios (veja Criptografia).
Encryption.Iterations must be at least 10000
Sintoma. O bootstrap falha com uma mensagem de iterações baixas.
Causa raiz. Erro de digitação — 600 em vez de 600000 em Encryption:Iterations.
Correção. Use 600.000 (recomendação da OWASP de 2023) ou mais.
O serviço inicia, mas o /api/ready retorna 503 persistentemente
Sintoma. O Get-Service mostra Iniciado / o systemctl mostra ativo, mas o /api/ready retorna
503.
Causa raiz. Um probe de prontidão está falhando. O corpo da resposta lista cada probe pelo nome, com
ok verdadeiro ou falso — banco de dados, pasta de entrada, licença.
/api/ready/detailsO /api/ready anônimo agora traz apenas o veredito: ready e, para cada verificação, o name e o ok.
O detail de cada verificação — que expunha o host do SQL Server, cada compartilhamento de entrada e o
local de um certificado degradado — fica em GET /api/ready/details, protegido pela chave de API
(X-API-Key) ou por uma sessão de operador, com a mesma regra de 200 / 503. Um monitor que lia o detail
na rota anônima passa a usar a rota de detalhes e a enviar o header. Readiness:RequireApiKey = true
coloca também o próprio /api/ready atrás da chave — deixe desligado onde o probe não consegue enviar o
header (o Health check do App Service não consegue).
Se o problema já tiver se resolvido quando você for verificar, o log durável guarda o ocorrido: toda
mudança de veredito de uma verificação é registrada uma vez, como Readiness check <nome> went red: <detalhe>
em Warning e Readiness check <nome> recovered em Information. Um vermelho persistente é registrado uma
única vez, e não a cada consulta; por isso, busque pelo nome da verificação em vez de ler as últimas linhas.
Correção. Inspecione o /api/ready/details e, depois:
| Probe que falhou | Onde procurar |
|---|---|
database | O detalhe indica o banco verificado — SQLite (…) ou SQL Server (servidor/banco). Com Sqlite: a conta de serviço tem permissão de escrita no caminho dentro de Storage:Root? Com SqlServer: o servidor está acessível, e o login ainda autentica? Quando o detalhe diz unreachable com um tipo de exceção depois do nome do banco, o log durável registra essa exceção, com a mensagem, em Warning — uma linha por probe que falhou; leia a primeira. |
input-folder:<nome> | A pasta existe? A conta de serviço tem permissão para listá-la? Semântica estrita — qualquer pasta ausente ou Stopped reprova a resposta inteira. |
storage-share:<conta>/<compartilhamento> | Somente com compartilhamento de trabalho remoto. Credencial, conectividade de rede ou a string de escopo da atribuição de role — veja Segurança. |
work-share-owner | Somente com compartilhamento de trabalho remoto. Outra instância detinha o marcador na inicialização, ou não foi possível fazer a reivindicação. Veja abaixo. |
license | A licença do PKI foi carregada? A impressão digital aparece no banner de resumo de prontidão; se estiver ausente, a string de licença foi rejeitada no boot. |
Algumas linhas informam ok: false sem transformar a resposta em 503, de propósito: um 503 tiraria a
instância do balanceador de carga, e o dashboard necessário para corrigir o problema é servido justamente
por essa instância. São elas: signing-profile:<nome> (um perfil degradado — veja
Um job falha com profile.degraded), profile-input-folder:<nome>
(um perfil vinculado a uma pasta que este host não configura) e, no modo cluster, cluster-instance (uma
instância deslocada que está se retirando). Uma linha signing-profile-keyless:<nome> é ok: true por
design, e uma pasta de entrada que nenhum perfil escolheu aparece como verde. Configure alertas para as
entradas individuais de checks[], e não apenas para o booleano ready de nível superior.
A inicialização falha com Signing:Profiles[N].Approval …
Sintoma. O host se recusa a iniciar com uma mensagem que cita uma chave de aprovação.
Quando isto pode acontecer. Desde que os perfis passaram para o banco operacional (2.1.0),
Signing:Profiles[] só é importado no primeiro boot com a tabela de perfis vazia, e estas regras são
verificadas nesse momento. Depois disso, as mesmas regras recusam o salvamento do formulário
Editar aprovação do perfil no dashboard, com o mesmo texto; editar o arquivo de configuração não altera mais
nenhum perfil.
Causas raiz, todas recusadas antes de o primeiro job rodar:
| A mensagem cita | Correção |
|---|---|
Approval sem CheckCNAB240 | Acrescente "CheckCNAB240": true ao mesmo perfil. Um aprovador que não pode ver o valor não está aprovando nada de significativo. |
Um pool Approvers vazio | O pool é obrigatório e não pode ser vazio quando Approval está presente. |
MinimumApprovers abaixo de 1 ou maior que o pool | Um quórum maior que o pool nunca pode ser atingido, então todo job ficaria retido para sempre. |
| Um e-mail malformado, ou o mesmo e-mail duas vezes | Uma mesma pessoa em duas vagas do pool poderia atingir sozinha um quórum de dois. |
| Um CPF cujos dígitos verificadores não conferem | Um erro de digitação identifica outra pessoa, e a linha de auditoria resultante parece tão legítima quanto uma correta. |
Um ExpiresAfter não positivo | Use o formato d.hh:mm:ss, por exemplo "2.00:00:00". |
A inicialização avisa has an approval wait budget of …, ou o prazo de um job retido está a semanas de distância
Sintoma. O banner avisa sobre um prazo de espera longo, ou o prazo de decisão de um job está muito mais distante do que o pretendido.
Causa raiz. A forma de escrever o TimeSpan. Um valor de três componentes só é hh:mm:ss enquanto o
primeiro número for 23 ou menos; de 24 em diante, o .NET o lê como dias, então "48:00:00" são
quarenta e oito dias.
Correção. Escreva o componente de dias: "2.00:00:00" — no formulário Editar aprovação do perfil no
dashboard, já que depois do primeiro boot o arquivo de configuração não altera mais um perfil armazenado.
O boot é o único momento em que isso é sinalizado — as demais telas só mostram o prazo quando um job já
ficou retido com ele, e o prazo de espera fica congelado nesses jobs. A correção vale apenas para jobs
novos: cancele e processe de novo tudo o que já estiver retido com a janela errada.
A inicialização é recusada porque tanto um caminho quanto um blob estão configurados
Sintoma. O boot falha dizendo que Path/CerPath e Blob são mutuamente exclusivos — ou que
nenhum dos dois está definido.
Correção. Defina exatamente um dos dois. Veja Certificados.
A inicialização falha com uma mensagem de configuração do Azure Files
Sintoma. O boot falha citando uma chave de Storage:AzureFiles ou Storage:Inputs[N].
Causas raiz. Um provider ou modo de credencial não reconhecido; um bloco de credencial incompleto para
o modo escolhido; um compartilhamento NFS (só SMB é aceito); um caminho azurefiles:// em Storage:Root,
Logging:File:Path ou — com Database:Provider = Sqlite — ConnectionStrings:Default; uma barra
invertida no Path de uma pasta remota; Directory definido em uma pasta de entrada; uma pasta
AzureFiles que fica sem nenhum intervalo de polling; ou uma pasta de entrada cujo caminho
coincide com uma das raízes de trabalho (output, ou prod/output com um prefixo Directory).
Este último caso é recusado porque, do contrário, apagaria um artefato assinado a cada iteração enquanto
informaria todos os jobs como Completed.
Uma implantação que antes iniciava agora é recusada, citando uma pasta de entrada monitorada
Sintoma. Após uma atualização, o boot falha citando um caminho de Storage:Inputs[N] que coincide com
uma das raízes de trabalho — output/, processing/ ou error/.
Diagnóstico. Esta recusa vale para todos os providers de armazenamento, não apenas para o Azure
Files, e é uma mudança proposital: uma configuração assim monitorava o diretório em que grava os
artefatos finalizados; com isso, cada arquivo assinado era reingerido, assinado de novo e então
apagado como o "original" da iteração seguinte. A implantação parecia saudável e informava todos os
jobs como Completed o tempo todo.
Correção. Aponte a pasta de entrada para algum lugar fora das raízes de trabalho. Antes de editar,
compare o output/ com o que os destinatários de fato coletaram — a recusa mostra que a configuração
estava errada, mas não há quanto tempo ela vinha destruindo artefatos.
Um arquivo é recusado com job.path-too-long
Sintoma. Um upload ou um arquivo monitorado é rejeitado, com uma mensagem que cita o limite de 850 caracteres de comprimento de caminho.
Diagnóstico. O caminho original do job é registrado no banco operacional, e um caminho acima desse limite não pode ser registrado. Agora ele é recusado no momento em que o arquivo é recebido, em vez de ser aceito e falhar depois, em todos os providers de banco de dados; assim, a falha chega a quem enviou o arquivo enquanto ainda é possível fazer algo a respeito.
Correção. Reduza o aninhamento de diretórios ou o nome do arquivo. Árvores profundas, particionadas por
data, com um prefixo Directory do Azure Files são a causa mais comum, já que o prefixo entra na conta.
O probe de compartilhamentos informa um compartilhamento inacessível na inicialização
Sintoma. O banner mostra azure shares = 1 of 2 reachable, o /api/ready está vermelho em uma linha
storage-share: (cujo detalhe, em /api/ready/details, é a própria frase do serviço de armazenamento), e
o host iniciou mesmo assim.
Causa raiz. Credencial, conectividade de rede ou escopo da role. A mais comum é a string de escopo:
uma atribuição criada com a grafia shares, do plano de gerenciamento, em vez de fileshares, do plano
de dados, é aceita sem erro e não concede nada, e depois falha como AuthorizationPermissionMismatch.
Correção. Compare a string de escopo antes de rotacionar qualquer coisa e confirme que a identidade
tem Storage File Data Privileged Contributor — uma role somente leitura não basta nem para uma
pasta de entrada. O host iniciar degradado, em vez de se recusar a iniciar, é proposital: um
compartilhamento fora do ar às 03:00 não pode transformar uma reinicialização em um serviço que não sobe.
A autenticação falha
401 Unauthorized de todo endpoint
Sintoma. Toda requisição retorna 401 { code: "auth.invalid-credentials" } ou
{ code: "auth.misconfigured" }.
Causas possíveis:
- Chave de API errada no header
X-API-Key. Compare byte a byte comAuth:ApiKey/Auth__ApiKey. Auth:ApiKeyvazia em tempo de execução (o caso de configuração incorreta). Procure no log porAuth:ApiKey is empty at runtime.- O cookie expirou — expiração deslizante de 8 horas. Entre de novo em
/login.
O login em /login redireciona em laço
Sintoma. Enviar o formulário de login leva a /login?error=....
Causas possíveis:
?error=invalid— chave de API errada. Confira de novo.?error=server—Auth:ApiKeyestá vazia em tempo de execução. Corrija a configuração e reinicie.
O login funciona, mas o dashboard desconecta imediatamente
Sintoma. O login é bem-sucedido, a página vai para /, e a navegação seguinte volta para /login.
Causa raiz. O cookie de sessão não está voltando porque um proxy reverso remove o header
Set-Cookie, ou o cookie está sendo marcado como Secure enquanto a requisição chegou à aplicação em
HTTP puro.
Correção. Garanta que o proxy reverso repasse os headers Set-Cookie e Cookie sem modificação. Se
o TLS terminar no proxy, defina X-Forwarded-Proto: https para que a aplicação marque o cookie como
Secure.
A inicialização falha com Auth:EntraId:… is required when the Auth:EntraId section is present
Causa raiz. A seção é ativada pela presença — basta escrevê-la para que as três chaves se tornem obrigatórias. "Presente, mas vazia" não significa desligada.
Correção. Forneça a chave que falta, ou remova a seção Auth:EntraId inteira para voltar ao login
por chave de API.
O login do Entra falha na Microsoft com AADSTS50011 (divergência de redirect URI)
Causa raiz. A URI de redirecionamento do registro de aplicativo não corresponde ao callback do host.
Correção. Registre uma URI de redirecionamento do tipo Web exatamente como
https://<seu-host>/signin-oidc — esquema, host, porta e caminho precisam coincidir com o endereço que o
navegador de fato acessa.
O login do Entra é bem-sucedido, mas cai em /access-denied
Causa raiz. A conta foi autenticada, mas não tem nenhuma das app roles. A aplicação exige a role independentemente da configuração do tenant.
Correção. Atribua Administrator ou Approver (ou ambas) no aplicativo empresarial. Os valores de
role no manifesto precisam coincidir exatamente com essas strings. Não há mapeamento por grupo de
segurança, de propósito.
Os eventos de auditoria de um operador do Entra dizem (anonymous), e o menu do usuário também
Sintoma. Um Administrator conectado cria ou edita um perfil, pausa o pipeline ou executa um backup,
e o evento operacional registra (anonymous). O menu do usuário mostra Conectado como (anonymous), e
uma execução manual de backup na página Backup mostra manual · (anonymous).
Causa raiz. Desde a 2.2.1, o nome registrado do operador é a claim preferred_username do token (o
UPN), e a sessão desse operador não recebeu o nome a partir dela. Antes da 2.2.1, os eventos de todos os
operadores do Entra saíam assim, qualquer que fosse o token, e essas linhas não podem ser corrigidas —
apenas eventos gravados por uma sessão iniciada depois da atualização trazem o autor.
Causas possíveis, em ordem de probabilidade:
- Uma sessão anterior à atualização. O nome fica no cookie de sessão, e o cookie tem expiração deslizante de oito horas; assim, um operador que permaneceu conectado durante a atualização mantém a sessão antiga. Saia e entre de novo uma vez. Nenhuma linha de log acompanha este caso.
- O token não trazia
preferred_username. O Bulk Signer sempre solicita o escopoprofile, que inclui a claim; portanto, o tenant a omitiu — consentimento do usuário aoprofilerecusado ou restrito, ou uma personalização de token no registro de aplicativo. Este caso é sinalizado: o login grava um aviso no log com os tipos de claim recebidos (nunca os valores); procure no log porpreferred_username. UmAdministratorconvidado é registrado com a forma#EXT#do UPN, o que está correto. - O host é anterior à 2.2.1. Atualize; depois, saia e entre de novo.
O nome de exibição, de propósito, não é usado como alternativa; por isso, os eventos continuam como
(anonymous) em vez de serem registrados com um nome que duas pessoas podem ter.
Um Approver do Entra entra, mas o portal está vazio
Causa raiz. A role dá acesso; mas quem decide quais jobs a pessoa vê continua sendo o pool congelado, comparado com o e-mail informado pelo diretório dela. O endereço dela não está em nenhum pool.
Correção. Compare o endereço na lista Approvers do perfil com o atributo de e-mail da conta. Para
contas de convidado, verifique se o atributo mail contém o endereço corporativo configurado no pool —
o UPN alterado com #EXT#, de propósito, não é usado como alternativa. Uma conta cujo token não traz
nenhuma claim de e-mail é recusada de imediato, com uma página que explica o motivo.
Depois de habilitar o modo Entra, os operadores são desconectados e o /api/auth/login para de funcionar
Não é uma falha. Ligar o modo invalida de uma só vez todas as sessões de navegador criadas por chave
de API, e um POST em /api/auth/login não emite cookie nem para uma chave correta — o endpoint está
desligado, não escondido. Planeje a transição como um "desconectar todo mundo". Clientes REST que usam
X-API-Key não são afetados.
Sair e entrar de novo acontece instantaneamente, sem pedir senha
Não é uma falha. Sair é uma ação apenas local: limpa a sessão do Bulk Signer e, de propósito, não encerra a sessão Microsoft da pessoa. Esse é o comportamento normal de SSO. Desde a 2.2.1, sair encerra, sim, a janela de verificação do segundo fator de um aprovador; assim, quando um colega entra de novo sem digitar senha em uma estação de trabalho compartilhada, o código volta a ser pedido.
A assinatura falha
Um job falha com profile.degraded
Sintoma. Os jobs vão de Queued → Failed com o erro profile.degraded. O histórico do job diz que o
perfil está degradado e cita o motivo. Jobs de outros perfis continuam sendo concluídos normalmente. O
banner de inicialização mostrou o mesmo perfil como DEGRADED, se ele já estava assim na inicialização,
e o /api/ready traz uma linha signing-profile:<nome> com ok: false — o motivo está em
/api/ready/details, com a chave de API.
Causa raiz. O certificado desse perfil não pôde ser aberto quando o host iniciou — um arquivo PKCS#12
ausente, uma senha errada, um thumbprint que não corresponde a nada no token ou no repositório, um Key
Vault inacessível ou um blob de material de assinatura ilegível. O motivo indica qual. Em um perfil do
método SignerFolder, mais três motivos terminam da mesma forma sem que o certificado tenha deixado de
abrir: um host sem configurações Signer:*, um certificado sem CPF e uma pasta do Signer que
não existe mais — veja
as configurações,
o CPF e
a pasta. Três
mensagens que vale a pena conhecer, para um certificado que não abriu:
-
Uma senha de PKCS#12 errada aparece como did not open with the PKCS#12 password given — check the PKCS#12 password on the profile, and if it is right, supply the file again, seguida da frase do próprio PKI SDK, que fala em PIN incorreto: o SDK usa uma só palavra para a senha de PKCS#12 e para o PIN de token, e o perfil não tem PIN a corrigir. A segunda metade da mensagem existe porque a verificação por trás dela é a tag de integridade do arquivo, e um arquivo danificado falha nela da mesma forma, mesmo com a senha certa.
-
Um PKCS#12 com o envelope moderno aparece como is encrypted with PBES2, which the signing library does not open. A biblioteca de assinatura só abre o envelope clássico, e tanto o OpenSSL 3, por padrão, quanto uma exportação do Windows configurada como AES256-SHA256 gravam PBES2 / AES-256. No Windows, exporte de novo a partir do repositório de certificados com a opção TripleDES-SHA1. A partir do
.pfxque você tem, exporte de novo pelo OpenSSL e forneça o novo arquivo:openssl pkcs12 -in modern.pfx -nodes -passin pass:<senha> -out tmp.pemopenssl pkcs12 -export -legacy -in tmp.pem -passout pass:<senha> -out signing.pfxshred -u tmp.pem # a chave privada está em claro no tmp.pem(
-legacyprecisa do provider legado do OpenSSL 3; em uma build sem ele,-keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DESproduz um envelope clássico que a biblioteca também abre.) A mesma frase aparece também com uma senha errada, de propósito — o envelope é o impedimento, qualquer que seja a senha. -
Um arquivo, blob ou upload vazio é recusado antes de qualquer decodificação, com Certificate file '…' is empty (0 bytes), indicando o local — em geral, um arquivo provisório criado enquanto se esperava o arquivo real, ou uma cópia que não chegou ao fim.
Isto não é um arquivo com problema. profile.degraded e cnab240.invalid são as duas falhas mais
fáceis de confundir, e as soluções não têm nada em comum: um perfil degradado significa que todo arquivo
roteado para ele vai falhar até que a implantação seja corrigida, enquanto um arquivo recusado significa
que só aquele arquivo precisa ser exportado de novo. Uma nova tentativa do job antes de corrigir o
certificado falha exatamente da mesma forma.
Também não é um perfil sem chave. Um perfil cuja regra de aprovação faz os aprovadores assinarem
(Approval.Signers = Approvers) não tem certificado próprio, por design: o banner começa a linha dele com
KEYLESS em vez de DEGRADED, o /api/ready o mostra em uma linha signing-profile-keyless:<nome> com
ok: true, e nenhum job nele falha com profile.degraded. O que falha nesse caso, com código próprio, é
um envelope de assinaturas dos aprovadores ausente (approval.signatures-missing) ou um conjunto de
assinantes congelado que o pipeline não consegue atender (approval.signer-set-unsupported). Mais um
código pertence a este caso: um job que ficou retido antes de a regra passar para Approvers foi
congelado com a chave do perfil e, em uma instância que iniciou depois da mudança — e que, por isso,
nunca abriu uma chave —, falha com profile.key-unavailable. Voltar a regra para um conjunto com chave e
reiniciar resolve; uma nova tentativa em uma instância que ainda tem a chave também.
Correção.
- Leia o motivo. Ele aparece no banner de inicialização, no log em
Critical, em/api/ready/details, na página do próprio perfil em/profiles/<nome>e no histórico do job que falhou — os cinco dizem a mesma coisa. - Corrija o certificado. Se as coordenadas estão erradas — um caminho digitado errado, um thumbprint que não corresponde a nada, o cofre errado —, corrija-as nessa página, em Editar certificado; o salvamento as armazena e marca o perfil como aguardando reinicialização. Se as coordenadas estão certas e o material não, os modos de falha são os de sempre: veja O boot é bem-sucedido, mas todo job falha com "Certificate not found by thumbprint", Falha de autenticação ou autorização no Azure Key Vault na inicialização e Um blob de material de assinatura não pode ser lido na inicialização.
- Reinicie o serviço. O certificado é aberto uma única vez, na inicialização, e nunca é recarregado; por isso, nem corrigir o arquivo nem salvar novas coordenadas muda nada em um host em execução — o aviso no perfil diz exatamente isso.
- Faça uma nova tentativa dos jobs que falharam, ou coloque os arquivos de volta na pasta monitorada. Os arquivos de entrada continuam no lugar: nada foi assinado, então nada justificava apagá-los.
Não é motivo para tirar a instância de serviço. A linha do /api/ready informa ok: false, mas
não transforma a resposta em 503 — veja
O serviço inicia, mas o /api/ready retorna 503 persistentemente.
Um perfil está degradado dizendo que um segredo armazenado não pôde ser descriptografado
Sintoma. O banner de inicialização mostra um ou mais perfis como DEGRADED, com um motivo que cita
Signing:ProfileSecretsKey — "A stored signing profile secret (Pkcs12Password) could not be
decrypted" — e o /api/ready traz uma linha signing-profile:<nome> com ok: false. Os jobs
roteados para esses perfis falham com profile.degraded. O host iniciou, e todos os perfis sem
segredo continuam assinando normalmente. Na página do perfil, o painel de certificado mostra as
coordenadas — o caminho, o thumbprint, o endpoint do cofre — e nenhuma linha de segredo.
Causa raiz. Signing:ProfileSecretsKey está definida, mas não tem o valor com o qual os segredos desses
perfis foram salvos. Quatro situações causam isso, e elas são indistinguíveis de propósito — a
descriptografia é autenticada, então uma chave errada e uma linha adulterada têm exatamente a mesma
aparência:
- a chave foi rotacionada e o material armazenado não foi digitado de novo depois;
- o banco operacional foi restaurado ou copiado de uma implantação com uma chave diferente;
- a chave foi digitada errado, quase sempre em
Signing__ProfileSecretsKey, em que é fácil errar o sublinhado duplo; - alguém editou uma coluna protegida diretamente, ou uma restauração moveu bytes entre colunas.
Esta não é a recusa por chave ausente
(A inicialização é recusada porque Signing:ProfileSecretsKey não está definida).
Aqui a chave está definida, e definir outra não vai ajudar: os valores armazenados foram gravados com a
antiga.
Correção, se você ainda tem a chave original.
- Restaure-a —
Signing__ProfileSecretsKey, exatamente como era. Verifique se há espaços em branco no final ou um sublinhado simples onde são necessários dois. - Reinicie o serviço. O certificado é aberto uma única vez, na inicialização, e nunca é recarregado.
Correção, se a chave foi perdida ou rotacionada de propósito. Não há como recuperar os valores em si; isso é intencional, e não uma lacuna. Para cada perfil citado no banner:
- Abra a página do perfil no dashboard e clique em Editar certificado.
- Digite de novo a senha do PKCS#12, o segredo de aplicativo do Key Vault ou a credencial do blob, ou envie o PKCS#12 de novo. Um campo em branco mantém o valor armazenado, que não é o que você quer aqui — digite o valor.
- Salve. O perfil é marcado como aguardando reinicialização.
- Reinicie o serviço quando todos os perfis afetados tiverem sido preenchidos de novo.
- Faça uma nova tentativa dos jobs que falharam, ou coloque os arquivos de volta na pasta monitorada.
Nada foi assinado, então os arquivos de entrada ainda estão em
input/.
Um perfil sem segredo — um PFX sem senha, um token PKCS#11, um certificado do repositório do Windows — não é afetado e não precisa de nenhuma ação.
Rotacionar Signing:ProfileSecretsKey invalida de uma só vez todos os segredos de perfil armazenados.
Faça a rotação preenchendo de novo o material de cada perfil com a nova chave enquanto a antiga ainda
funciona, e desative o valor antigo apenas quando nada mais estiver protegido por ele.
Certificados de teste da Lacuna (Turing / Fermat) são recusados
Sintoma. Com os certificados de teste da Lacuna, todo job do perfil falha com um erro de cadeia ou
de confiança, ou todo Assinar e aprovar de um aprovador é recusado como approval.certificate-invalid,
citando uma raiz não confiável. A linha trust set do banner mostra production.
Causa raiz. Não é uma falha. O produto publicado valida toda assinatura apenas contra as raízes da
ICP-Brasil; a raiz de teste da Lacuna só é confiável com Signing:TrustLacunaTestRoot = true (disponível
a partir da 2.3.0). Um único conjunto de confiança vale para o host inteiro: a chave do perfil no momento
da assinatura, o verificador depois e o certificado de um aprovador.
Correção. Em um host de homologação, defina Signing__TrustLacunaTestRoot=true e
ASPNETCORE_ENVIRONMENT=Staging — a chave sozinha é recusada com o nome de ambiente Production (veja
a entrada em O serviço não inicia).
Em qualquer ambiente que assine documentos reais, use certificados reais; a chave não serve para isso.
O boot é bem-sucedido, mas todo job falha com "Certificate not found by thumbprint"
Sintoma. Todo job vai de Queued → Failed. A mensagem de erro menciona uma divergência de
thumbprint. Desde a 2.1.0, isso é detectado quando o certificado do perfil é aberto na inicialização: o
banner mostra o perfil como DEGRADED com o thumbprint no motivo, e os jobs dele falham com
profile.degraded.
Causa raiz. O thumbprint configurado não corresponde a nenhum certificado visível à origem configurada.
Diagnóstico:
| Origem | Comando |
|---|---|
Pfx | openssl pkcs12 -in /etc/bulksigner/signing.pfx -nokeys -passin pass:<senha> — o arquivo carrega? |
Pkcs11 | pkcs11-tool --module /caminho/para/driver.so --list-objects --type cert --login --pin <pin> — o certificado existe no token? |
WindowsStore | Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object Thumbprint -eq <thumbprint> |
Corrija o thumbprint configurado ou importe o certificado que falta.
A assinatura falha com "module load failed" / "C_Initialize" do PKCS#11
Sintoma. O bootstrap é bem-sucedido, mas a primeira tentativa de assinatura dá erro com uma falha de inicialização do PKCS#11.
Causas possíveis:
- O
.so/.dlldo fabricante não está no host, no caminho indicado emModulePath. - (Docker) A biblioteca do fabricante não está montada no container — veja Certificados.
- (Linux) O token exige o
pcscdem execução —sudo systemctl start pcscd.
A assinatura falha com "Access is denied" ao ler uma chave privada do Windows
Sintoma. A assinatura lança CryptographicException: Access is denied. a partir do repositório do
Windows.
Causa raiz. A conta virtual do serviço NT SERVICE\LacunaBulkSigner não tem acesso à chave privada.
Correção. certlm.msc → certificado → Todas as Tarefas → Gerenciar Chaves Privadas → Adicionar
NT SERVICE\LacunaBulkSigner → conceder Leitura.
Jobs do Azure Key Vault falham com throttling (HTTP 429) ou erros transitórios de rede
Sintoma. Com Source = AzureKeyVault, os jobs falham em vez de travar, com um erro do Azure —
HTTP 429 (Too many requests), um timeout ou uma falha de resolução de nome. Em geral, coincide com uma
rajada de arquivos ingeridos.
Causa raiz. Cada assinatura é uma chamada remota ao Key Vault; por isso, a vazão é limitada pelos
limites de requisição do cofre, e não pela CPU local. Um Pipeline:MaxConcurrency alto somado a um lote
grande pode ultrapassar esses limites. Uma indisponibilidade do cofre ou uma perda da conectividade de
saída produz o mesmo quadro.
Correção.
- Reduza o
Pipeline:MaxConcurrency(comece com algo entre 4 e 8) e meça de novo. Ao contrário do caso do PKCS#11, não há motivo de correção para baixar para1— trata-se de um limite de requisições, não de um conflito de sessão. - Faça uma nova tentativa dos jobs afetados quando o cofre estiver acessível. Throttling e indisponibilidades são transitórios, e os arquivos de entrada não foram alterados; a nova tentativa é manual por design (veja Operação).
- Confirme que a saída para
*.vault.azure.netelogin.microsoftonline.comestá estável, inclusive por qualquer proxy. - Se o objetivo é uma vazão sustentada, confira os limites de transação documentados do cofre para o tipo de chave em uso — operações RSA têm tetos mais baixos que EC.
Um verificador externo rejeita uma assinatura do Bulk Signer
Sintoma. Um PDF assinado é validado no Lacuna PKI SDK, mas um verificador de terceiros informa que a política é desconhecida ou que a cadeia está incompleta.
Causas possíveis:
- O verificador exige uma política diferente da padrão (por padrão, o Bulk Signer assina com a ADR-Básica). Combine com o sistema de destino qual política é esperada.
- Falta uma AC intermediária no verificador. O Bulk Signer assina com a cadeia implícita no certificado; o verificador monta a cadeia a partir do próprio repositório de confiança.
Pipeline / worker
Os jobs entram na fila, mas nunca entram em Processing
Sintoma. O bulksigner_jobs_in_flight fica em zero; os jobs ficam em Queued.
Causas possíveis:
- O pipeline está pausado. O
GET /api/pipeline/stateretorna{ paused: true }. Retome comPOST /api/pipeline/resume. - O worker não está saudável. O log mostra as linhas de iteração do worker; se elas pararam, o worker pode ter caído (raro; procure uma exceção registrada).
A pausa responde pipeline.state-missing (SQL Server)
Sintoma. Em uma implantação com Database:Provider = SqlServer, o POST /api/pipeline/pause responde
pipeline.state-missing, o log registra PipelineState singleton row is missing em Critical, e o
pipeline continua rodando, não importa o que o operador peça.
Causa raiz. Bancos SQL Server criados antes da 2.4.3 nunca receberam a linha de estado do pipeline sobre a qual a pausa e a retomada atuam. Correção. Atualize para a 2.4.3 ou posterior: a linha é inserida por uma migração aplicada no próximo boot.
Os jobs travam quando MaxConcurrency > 1 com um token PKCS#11 ou CSP do Windows
Sintoma. Com Pipeline:MaxConcurrency > 1 e Signing:Certificate:Source = Pkcs11 (ou
WindowsStore), jobs em andamento ficam travados além da latência normal de assinatura, ou falham com
erros como CKR_SESSION_HANDLE_INVALID, Provider is busy ou Key container is in use.
Causa. A maioria dos tokens PKCS#11 (smart cards de uso pessoal, tokens USB) oferece uma única sessão por login. Chamadas de assinatura simultâneas de várias tarefas do worker disputam essa única sessão. CSPs de software do Windows costumam ser thread-safe; CSPs baseados em smart card não são. O banner de inicialização avisa quando essa combinação está configurada.
Correção. Defina Pipeline:MaxConcurrency: 1 no appsettings.Production.json (ou deixe sem definir,
para usar o padrão) e reinicie o serviço. Se a documentação do fabricante afirma que o token suporta
várias sessões e você quer vazão com concorrência, procure o fabricante com as linhas de log da falha
para confirmar a configuração. Veja
Certificados.
Linha de log: "claim lost to a concurrent writer"
Sintoma. O log mostra, no nível Information, que a reivindicação de um job foi perdida para outro
processo que gravou ao mesmo tempo. O job está em algum estado terminal (normalmente Canceled, se um
operador o cancelou).
Causa. Este é o comportamento esperado, não um erro. A mensagem aparece quando o worker já tinha lido
uma linha Queued, mas, entre a leitura e a gravação, outro processo (o endpoint de cancelamento ou outro
worker) atualizou a linha. O controle de concorrência otimista detecta a disputa e o worker desiste. A
frequência deveria ser muito baixa — vê-la dezenas de vezes por dia sugere um cliente fazendo repetições
em excesso no endpoint de cancelamento.
Correção. Nenhuma. Se os volumes estiverem anormalmente altos, audite os clientes que fazem as chamadas.
O observador não pega arquivos colocados em uma pasta de entrada configurada
Sintoma. Arquivos aparecem em uma das pastas de Storage:Inputs[].Path, mas nenhum job é criado.
Causas possíveis:
- A extensão do arquivo está na lista efetiva de ignorados — a base global
WatchedFolder:IgnoredExtensions(.tmp,.part,.crdownload,.swp) somada àsIgnoredExtensionsde cada pasta. Renomeie o arquivo, ou tire-o da pasta e coloque-o de volta. - O prefixo do nome do arquivo está na lista efetiva de prefixos (padrão global:
.,~$). - O produtor ainda está gravando o arquivo. O detector de estabilidade exige
WatchedFolder:StabilityRequiredSamplesamostras idênticas consecutivas antes de enfileirar. Espere, ou façaPOST /api/rescan(ouPOST /api/rescan?folder=<nome>para uma única pasta) depois que a gravação terminar. - O observador da pasta está em
Status: Stopped. Veja abaixo. - Nenhum perfil de assinatura escolheu a pasta. O card dela mostra um chip cinza
sem perfilno lugar do chip do perfil. Veja Arquivos ficam em uma pasta cujo card dizsem perfil. - O perfil de assinatura da pasta está desabilitado. Veja
Arquivos são recusados com
profile.disabled. - O job mais recente do arquivo terminou como
FailedouCanceled. O observador não oferece esse arquivo de novo por conta própria (desde a 2.11.0 paraFailed: antes, uma pasta com polling gerava um novo jobFaileda cada ciclo enquanto a causa persistisse). Processe-o de novo com uma nova tentativa, uma nova varredura ou um upload — isso vale também para um arquivo corrigido colocado com o mesmo nome. - O nome já está em uso por um job concluído. O arquivo vira um job, sim, mas um job que falha na
hora — veja Um arquivo falha na hora com
file.already-processed. - (Docker) Problema de permissão do bind mount — o UID do container (1654) precisa conseguir ler os
arquivos colocados pelo processo do host. Execute
chown -R 1654:1654 ./datano host.
Arquivos ficam em uma pasta cujo card diz sem perfil
Sintoma. Arquivos se acumulam em uma pasta configurada e nenhum job é criado. A página Pastas de
entrada mostra a pasta com um chip cinza com o texto sem perfil — nenhum perfil escolheu esta pasta; o
/api/folders retorna profileName: null e, assim que o observador percebe, "status": "Unassigned"; o
/api/ready está verde para essa pasta; uma nova varredura informa a pasta como unassigned: true,
com todas as contagens em zero; e o log registra um Warning, Watched folder '<nome>' is unassigned — no signing profile has chosen it.
Causa raiz. Desde a 2.2.0, uma pasta monitorada é assinada com o perfil que a escolheu, uma pasta por
perfil — e nenhum perfil a escolheu. Pode ser que a importação do primeiro boot a tenha deixado assim — a
pasta indicava um perfil que Signing:Profiles[] não declarava, ou uma pasta anterior já tinha ficado com
o perfil indicado, e o banner de inicialização disse qual —, que um perfil a tenha liberado depois, pela
página dele, ou que a tabela de perfis tenha sido importada por uma versão anterior à 2.2.0, que não
registrava vínculos de pasta. Uma pasta sem perfil não passa a usar o default.
Por que a pasta não aparece como quebrada. Ela não está quebrada: o armazenamento responde, o observador está aguardando em vez de ter falhado, e uma linha de prontidão vermelha faria um orquestrador tirar a instância de serviço por causa de uma pasta que ninguém pediu para ela monitorar ainda. Os arquivos estão onde o produtor os deixou e são listados assim que um perfil escolhe a pasta.
Correção. Abra um perfil na página Perfis de assinatura do dashboard, clique em Editar
comportamento, escolha a pasta em Pasta de entrada e salve; ou crie um perfil já com a pasta
escolhida. O observador começa em um ou dois intervalos de polling e lista tudo o que estiver na pasta —
sem reinicialização e sem nova varredura. Editar Storage:Inputs[].Profile não resolve depois do
primeiro boot: essa chave só é lida na primeira importação, e o boot avisa que ela está sendo ignorada.
Veja Operação.
Arquivos são recusados com profile.disabled
Sintoma. Arquivos se acumulam em uma pasta configurada e nenhum job é criado, mas a pasta não está
parada: o card dela está verde, o /api/ready está normal, e o console exibiu uma linha com o texto
files are not being enqueued — signing profile '<nome>' is disabled. Um upload para esse perfil responde
409 com code = "profile.disabled"; o mesmo acontece com uma nova tentativa de um job que o usava.
Causa raiz. Alguém desligou a chave aceitar novos trabalhos na página do perfil. Isso impede que novo trabalho seja roteado para ele, e nada mais: os jobs já enfileirados no perfil foram processados até o fim, e nenhum arquivo foi modificado — cada um está onde o produtor o deixou.
Correção. Reabilite o perfil na página dele — a próxima passada da pasta ingere tudo o que estiver nela, inclusive os arquivos recusados enquanto ele esteve desligado —, ou escolha a pasta na página de outro perfil. (O salvamento se recusa a desabilitar um perfil alimentado por uma pasta, indicando qual; portanto, este estado só surge de um vínculo de pasta feito depois de o perfil ter sido desabilitado.)
Um arquivo falha na hora com file.already-processed
Sintoma. Um arquivo colocado em uma pasta monitorada vira um job que já nasce Failed com
file.already-processed, indicando o job que detém o nome, e o arquivo é movido para a pasta
error/<jobid>/ do novo job. Um upload responde 409 com o mesmo código e não armazena nada. Uma nova
varredura contabiliza esses casos em alreadyProcessed.
Causa raiz. Desde a 2.13.0, um arquivo que chega com um nome já usado por um job Completed ou ainda
ativo nunca é assinado. A comparação vale para o host inteiro — todas as pastas monitoradas, todos os
perfis e todos os uploads — e ignora maiúsculas e minúsculas, porque todos gravam na mesma pasta
output/. Um job Failed ou Canceled não reserva nome.
Correção. A nova tentativa é recusada para esta falha (uma nova tentativa é isenta da regra e,
portanto, assinaria o arquivo que a regra recusou). Para aceitar o nome de novo, exclua o job que o
detém na página Jobs. Para um produtor que, legitimamente, reutiliza todo dia um mesmo nome de
arquivo fixo, desligue a regra com Pipeline:RejectAlreadyProcessedFileNames = false. Veja
Operação.
Uploads são recusados com upload.disabled, ou não há botão Enviar arquivos
Não é uma falha. Upload:Enabled = false (disponível a partir da 2.10.0) desliga o upload dos dois
lados de uma vez: o POST /api/files responde 409 com upload.disabled, e a página Jobs não exibe
o botão Enviar arquivos. O host recebe arquivos apenas das pastas monitoradas; a nova varredura e a
nova tentativa não são afetadas. A chave é lida uma única vez, no boot; por isso, religar os uploads
exige reinicialização.
Um observador de pasta está em Status: Stopped
Sintoma. Arquivos se acumulam em uma pasta configurada, mas nenhum job é criado; a página Pastas de
entrada mostra o card da pasta com um chip vermelho parado e uma mensagem de último erro. O
/api/folders retorna "status": "Stopped" para essa pasta. O /api/ready retorna 503 com a pasta
problemática no array checks.
Causa raiz. O observador dessa pasta atingiu o limite de falhas consecutivas de enfileiramento por pasta (10 por padrão) — normalmente um caminho de armazenamento comprometido (o NFS caiu, o compartilhamento ficou somente leitura, o disco da montagem do SQLite encheu).
A falha do observador fica isolada nessa pasta — as outras pastas continuam ingerindo e o host continua
no ar. A contrapartida é que um operador que não acompanha o /api/ready nem a página Pastas de
entrada pode deixar de notar uma pasta degradada por muito tempo. Consulte o /api/ready a partir de
um sistema de monitoramento externo.
Diagnóstico e correção:
- Leia o texto do último erro em
GET /api/folders(ou no card da página Pastas de entrada). - Corrija a causa subjacente (monte de novo o compartilhamento, libere espaço em disco, corrija o caminho).
- Reinicie o serviço — o observador não volta sozinho depois de parar, porque o problema subjacente em geral não é transitório.
Um arquivo foi parar em error/<jobid>/
Sintoma. A página de detalhe do job mostra Failed com uma mensagem de erro; o diretório
processing/ foi movido para error/<jobid>/.
Diagnóstico:
- Leia a mensagem de erro do job (no dashboard ou em
GET /api/jobs/{id}). - Procure em
error/<jobid>/o arquivo que estava em processamento — ele é preservado exatamente como o worker o deixou. - Leia o histórico do job para ver a linha do tempo completa das transições.
Correção: resolva a causa subjacente e, depois, faça POST /api/jobs/{id}/retry. A nova tentativa
cria um novo job Queued com ParentJobId definido; o job que falhou é mantido para auditoria.
Um job CNAB240 falha com cnab240.invalid
Sintoma. O job nunca chegou a um assinador; a linha do tempo lista as violações estruturais.
Causa raiz. O arquivo roteado por um perfil com CheckCNAB240 não é uma remessa válida do Banco do
Brasil — comprimento de registro errado, registros fora de ordem, um código de banco diferente de 001,
um segmento não reconhecido, uma contagem de trailer divergente, ou um retorno
(Código Remessa / Retorno = '2') colocado por engano em uma pasta monitorada.
Correção. Corrija o arquivo no sistema de origem e processe-o de novo por upload, nova tentativa ou nova varredura. A lista de violações na linha do tempo é limitada e avisa quando foi truncada. Veja CNAB240.
Um job CNAB240 falha com cnab240.payment-date-passed
Sintoma. Uma remessa estruturalmente válida é recusada logo antes da assinatura.
Causa raiz. A data de pagamento mais antiga do arquivo está no passado. O BB a recusaria ou a processaria em uma data que ninguém pretendia, e uma assinatura faria a data errada parecer intencional.
Correção. Exporte de novo a partir do sistema de origem, com datas atuais. Uma nova tentativa com o mesmo arquivo falha da mesma forma — as datas dentro dele não mudaram.
Se o seu banco processa um pagamento com data passada no próximo dia útil, o perfil pode, em vez disso,
desligar a verificação (a partir da 2.15.0): CheckCnab240PaymentDates = false no comportamento do perfil
(ou Signing:Profiles[].CheckCnab240PaymentDates para um perfil importado em um primeiro boot). O arquivo
passa, e a decisão fica registrada — no histórico do job, em um evento operacional
Cnab240PaymentDateCheckSkipped, no contador bulksigner_cnab240_payment_date_checks_skipped_total e em
uma linha de log em Warning. A configuração é lida no momento da assinatura; assim, uma mudança vale a
partir do próximo job, sem reinicialização. Veja CNAB240.
"Hoje" é a data local do host. Em um host em UTC com o pagador em America/Sao_Paulo, a virada do dia
acontece três horas mais cedo, e um arquivo com vencimento no dia começa a ser recusado às 21:00 no
horário local. Defina TZ=America/Sao_Paulo no container ou na unit do systemd.
Um aprovador é informado de que o registro de aprovação está incompleto
Sintoma. O clique ou a assinatura de um aprovador é recusado com Não é possível decidir sobre este
job — o registro de aprovação está incompleto. Contate quem opera o serviço. Pela REST, o código é
approval.job-incomplete. O job continua em AwaitingApproval.
Diagnóstico. Antes de aceitar uma decisão, o Bulk Signer confere o registro ao qual a decisão fica
vinculada, e algo necessário está ausente ou não confere mais: a regra congelada, o hash de conteúdo do
job, a cópia preparada em processing/<jobid>/ ou os bytes dela, o envelope de assinaturas dos
aprovadores ao lado dela, ou o thumbprint do certificado de uma linha aprovada. Abra a página do job como
operador: desde a 2.8.0, a seção Registro de aprovação executa as mesmas verificações e marca a que
falhou, com o valor encontrado e o caminho da pasta de processamento.
A verificação que falhou indica o que aconteceu. Um hash de conteúdo ausente muito provavelmente
significa que o job ficou retido em um perfil com CheckCNAB240 desligado — a interpretação do CNAB240 é
a única etapa que registra esse hash. O histórico do perfil vai mostrar a verificação sendo desligada
(Changed: CheckCNAB240 on → off) enquanto a regra de aprovação continuava valendo. Desde a 2.9.0, a
página do perfil recusa esse salvamento, e a etapa de aprovação faz esse job falhar com um código próprio
em vez de retê-lo (veja a entrada abaixo);
portanto, um job nesse estado ficou retido antes de qualquer uma das duas recusas existir. Qualquer
outra verificação que falhe significa que a linha ou a pasta foi modificada fora da aplicação — um
backup restaurado, um antivírus que colocou o arquivo em quarentena e o restaurou, um cliente de
sincronização, uma edição manual.
Correção. Cancele o job (botão Cancelar, ou POST /api/jobs/{id}/cancel). No caso do hash de
conteúdo, primeiro religue o CheckCNAB240 em Editar comportamento no perfil — ou remova a regra de
aprovação, se o perfil não deve ter etapa de aprovação — e depois processe o arquivo de novo por nova
varredura ou upload, para que ele seja interpretado, totalizado e aprovado do zero; o original ainda está
em input/. Por design, nada corrige o registro no lugar. Nas outras verificações, descubra o que tem
acesso de escrita ao banco operacional ou a processing/ por fora da aplicação e interrompa-o, ou o
próximo job retido terá o mesmo destino.
Um job fica em AwaitingApproval e nada acontece
Por si só, não é uma falha — o job está esperando uma pessoa e vai esperar indefinidamente, a menos
que o perfil defina Approval.ExpiresAfter. O que conferir:
- Alguém sabe? O produto não envia e-mail. Os aprovadores chegam a um arquivo retido pela própria fila
em
/approvals— pelo link permanente do portal (listado por aprovador na página Sistema) ou por um login do Entra. Desde a 2.9.0, a página do job não mostra mais um link de aprovação por job para copiar; a página anônima em/approve/<jobId>ainda existe para implantações que dependam dela, e tudo o que está em Segurança sobre como distribuí-la continua valendo. - O pool está certo? A página do job mostra o pool congelado no momento da retenção, e não a regra atual do perfil. Se as pessoas listadas estiverem erradas, cancele o job, corrija o perfil e processe o arquivo de novo — editar um perfil nunca muda o que um job retido exige. A coluna Aprovações da página Jobs (a partir da 2.12.0) mostra quantas aprovações ainda faltam para cada job retido.
- Acompanhe o
bulksigner_approvals_expired_total. Uma taxa de expiração crescente é o sinal de que os aprovadores não estão olhando a fila.
Um aprovador recebe "Esse endereço não está no grupo de aprovadores deste job"
Causa raiz. O endereço dele não está no pool congelado. Espaços no início ou no fim e maiúsculas não importam; qualquer outra diferença importa.
Correção. Compare com o pool exibido na página do job. A recusa é propositalmente genérica — um endereço malformado retorna o mesmo código — para que alguém que adivinhou um id de job não descubra nada sobre quem são os aprovadores.
Um job liberado falhou com approval.content-changed
Sintoma. O quórum foi atingido, o job voltou a Queued e, em seguida, falhou em vez de ser assinado.
Causa raiz. A cópia preparada em processing/<jobid>/ foi modificada depois que os aprovadores a
viram. A verificação de hash anterior à assinatura se recusou a produzir uma assinatura sobre bytes que
ninguém aprovou.
Correção. Não assine o arquivo de novo. Descubra o que gravou em processing/ e, depois, processe
de novo o arquivo original a partir de input/, para que ele seja interpretado, totalizado e aprovado do
zero. Esse contador deveria ficar em zero para sempre; qualquer outro valor merece investigação, e não
uma nova tentativa por cima.
Um job falhou com approval.content-unmeasured em vez de ficar retido
Sintoma. Um arquivo roteado para um perfil com etapa de aprovação vai para Failed em vez de
AwaitingApproval. O histórico diz que ele foi recusado antes da retenção porque a verificação CNAB240 do
perfil está desligada, a pasta está em error/<jobid>/, e o log registra Job … was refused before parking for approval: profile … requires approval but its CNAB240 check is off. Todo arquivo nesse perfil falha
da mesma forma.
Diagnóstico. O perfil tem uma regra de aprovação e CheckCNAB240 = false. A interpretação é a única
etapa que registra o hash de conteúdo ao qual uma decisão fica vinculada; sem ela, o job não tem com base
em que ficar retido — e um job retido sem esse hash nunca poderia ser decidido. Desde a 2.9.0, a etapa de
aprovação o recusa com um código próprio, e a página do perfil se recusa a salvar essa combinação,
qualquer que seja o lado editado; portanto, um perfil nesse estado foi editado antes de essa recusa
existir, ou teve a linha editada fora da aplicação.
Correção. Na página do perfil, religue o CheckCNAB240 em Editar comportamento, ou remova a regra
de aprovação em Editar aprovação, se o perfil não deve ter etapa de aprovação. Não é preciso
reiniciar: o próximo job reivindicado já roda com a regra corrigida. Depois, processe de novo os arquivos
que falharam, por nova varredura ou upload; os originais ainda estão em input/.
Um job falhou com approval.rejected em vez de ser cancelado
Causa raiz. A rejeição chegou depois de um worker já ter reivindicado o job; por isso, o pipeline
recusou a assinatura, em vez de o handler de aprovação cancelar o job. Processing não tem transição
válida para Canceled.
Não é uma falha. O arquivo está sem assinatura, que é a propriedade que importa. Corrija e envie de novo.
Um job foi cancelado com "Approval window expired."
Causa raiz. Ninguém decidiu dentro da janela ExpiresAfter do perfil.
Correção. A cópia preparada está em error/<jobid>/, o original ainda está em input/, e as
aprovações que chegaram a ser registradas continuam na página do job. A nova tentativa não se aplica
(ela só aceita Failed) — processe o arquivo de novo por nova varredura ou upload, o que cria um novo job
que fica retido e consulta o pool outra vez.
Uma pausa não estende a janela: o prazo de espera é contado em tempo de relógio, e não em tempo de atividade do pipeline; por isso, um pipeline pausado durante uma janela faz expirar os jobs cujas janelas se encerraram durante a pausa.
Um job foi concluído, mas o arquivo de entrada ainda está em input/
Não é uma falha. O arquivo foi reescrito enquanto estava com o job; por isso, o pipeline se recusou a
apagar algo que não conseguia provar ser o arquivo que processou. Procure job.input-diverged na linha do
tempo do job. O arquivo reescrito é devolvido à pasta monitorada e assinado como um job à parte.
Há dois casos em que a devolução é descartada, e o console avisa: um upload REST (nenhum observador é responsável pelo caminho dele) e uma pasta cujo observador não está rodando. Veja Operação.
Um arquivo em processing/ ou error/ não pode ser escrito nem apagado
Causa raiz. Em um compartilhamento de trabalho do Azure Files, a cópia preparada de um job ativo tem
um lease infinito que recusa gravações e exclusões de qualquer origem, inclusive das suas próprias
ferramentas de armazenamento. Enquanto o job está em andamento, esse é justamente o objetivo. A retenção
normalmente termina junto com o job; se o job está em estado terminal e o lease continua ativo, é porque
o encerramento da retenção falhou — o compartilhamento estava inacessível, a credencial tinha sido
rotacionada ou a realocação foi recusada —, e o log registrou isso quando o job terminou (por exemplo,
Cancel of job … could not end the hold on its staged copy ou Recovery: failed to relocate processing/…).
Correção. O lease fica na conta de armazenamento, e não neste processo; por isso, reiniciar o Bulk Signer não o libera. Confirme, pela página do job, que ele está em estado terminal; depois, quebre o lease e apague ou mova o arquivo normalmente:
az storage file lease break --account-name <conta> --share-name <compartilhamento> --path 'processing/<jobid>/<arquivo>'
ou, no portal, selecione o arquivo e use Break lease. Nunca quebre o lease da cópia preparada de um
job ativo: isso remove a proteção, e caberia então ao novo cálculo de hash anterior à assinatura
detectar a alteração — o job falharia com approval.content-changed em vez de assinar os bytes errados.
Uma árvore de trabalho local não tem esse lease — a retenção dela desaparece no momento em que o serviço
reinicia.
O /api/ready está em 503 com work-share-owner vermelho
Causa raiz. Outra instância detinha o marcador do compartilhamento de trabalho na inicialização. O banner, o log, a página Sistema e esta verificação indicam o host e o id de processo do detentor anterior.
Correção. Descubra se esse host e esse processo ainda estão rodando.
- É este host, e o processo não existe mais — a instância anterior não foi encerrada de forma limpa. Não há nada de errado agora. A linha continua vermelha durante toda a vida desta instância e volta ao normal no próximo boot após uma parada limpa; o marcador é reivindicado uma única vez e nada o lê de novo, então não há como obter uma resposta mais atual.
- É outro host, ou esse processo está ativo — você tem duas instâncias em um mesmo compartilhamento de trabalho, o que não é suportado. Pare uma delas e, depois, decida qual banco é o oficial. O estado de aprovação é o que exige ação rápida: um job retido existe no banco de apenas uma das instâncias.
Se, em vez disso, o detalhe da linha em /api/ready/details diz not claimed cleanly at startup: …,
não foi possível acessar o marcador — um compartilhamento inacessível ou uma credencial rotacionada. Nesse
caso, simplesmente não se sabe se outra instância o detém, e o desconhecido não é apresentado como a
resposta tranquilizadora. A linha storage-share: do próprio compartilhamento costuma dizer o motivo. A
reivindicação é tentada de novo no próximo boot, e não em segundo plano.
Um arquivo rejeitado não foi devolvido a output/
Sintoma. Um aprovador rejeitou um arquivo. O job está Canceled, mas o output/ não tem
<nome>.reject<ext>, e a página do job diz "Não foi possível devolver o arquivo à pasta de saída,
portanto a cópia preparada está na pasta de erro e o original continua na pasta de entrada." O console
mostra um aviso, e o log, uma entrada RejectionHandbackFailed com o motivo.
De longe, a causa mais provável: o nome já estava em uso. Um arquivo vetado costuma ser corrigido pelo
financeiro e reenviado com o mesmo nome; assim, uma segunda rejeição dele tenta gravar folha.reject.rem
onde o primeiro já está. O Bulk Signer se recusa a sobrescrever — esses bytes são de alguém — e, como
alternativa, deixa a cópia preparada em error/. A mensagem de log indica o destino.
O que é garantido quando isso acontece, e esta é a parte tranquilizadora: o veto continua valendo,
nada foi assinado, o arquivo anterior em output/ está intacto, os bytes desta rejeição estão íntegros em
error/<jobid>/, e o arquivo de entrada ainda está na pasta monitorada — ele só é excluído depois de
uma devolução bem-sucedida.
O que fazer. Colete ou arquive o output/<nome>.reject<ext> mais antigo e, depois, deixe a cópia
atual em error/ (a trilha de auditoria aponta para ela) ou mova-a você mesmo para output/ com um nome
de sua escolha. Não é preciso reiniciar nada.
Outras causas, todas mais raras e todas identificadas no log: o compartilhamento de trabalho parou de
responder entre a gravação e a movimentação; o processo não tem permissão de escrita em output/; em um
compartilhamento, um lease de outra pessoa no destino. Se, em vez disso, aparecer
RejectionHandbackFallbackFailed, nenhum dos dois destinos funcionou — o job continua em estado terminal
e correto, e processing/<jobid>/ precisa ser limpo manualmente. Um RejectionHandbackUnavailable em
Error é um defeito do produto, e não uma falha operacional: informe-o ao suporte da Lacuna Software.
Dashboard
Toda página é exibida, mas nenhum botão funciona, e o console do navegador mostra 404 em _framework/blazor.web.js
Sintoma. Em uma implantação Docker, o dashboard carrega e as tabelas são preenchidas, mas Enviar
arquivos, Tentar novamente, Cancelar, os filtros e todos os outros controles não respondem. O console
do navegador tem exatamente um erro: um 404 para /_framework/blazor.web.js. O /api/ready está verde e
o log do servidor não registra nada.
Causa. Imagens de container anteriores à 2.4.1 compiladas com o .NET 10 — entre elas a 2.3.2 e a 2.4.0 — foram publicadas sem o script de cliente do dashboard; por isso, as páginas eram exibidas, mas nunca se tornavam interativas. Instalações como serviço do Windows, systemd e em primeiro plano nunca foram afetadas.
Correção. Baixe a imagem 2.4.1 ou posterior e implante de novo. Para conferir uma imagem antes de
implantá-la: docker run --rm --entrypoint ls <imagem> /app/wwwroot/_framework precisa listar
blazor.web.js.
Criptografia
A descriptografia falha com um erro de divergência de tag
Sintoma. O destinatário roda o exemplo de descriptografia e recebe um erro de divergência de tag de autenticação.
Causas possíveis (qualquer uma basta):
- Senha errada. Confira com o
Encryption:Password/ a variável de ambiente configurada. - Salt errado. O destinatário precisa usar o mesmo salt em base64 que o servidor usou; trocar o salt invalida todos os envelopes anteriores.
- Número de iterações errado. Use exatamente o valor de
Encryption:Iterations. - O envelope foi truncado no caminho (por exemplo, por uma ferramenta que converte finais de linha em um arquivo binário). Obtenha de novo os bytes, sem nenhuma alteração.
A descriptografia falha com "Unknown magic"
Sintoma. O script do destinatário informa unknown magic.
Causa raiz. O arquivo baixado não é um envelope BSENC — na maioria das vezes, o operador baixou por engano o texto claro de um job não criptografado.
Correção. Confirme a flag outputEncrypted do job com GET /api/jobs/{id}. Se o job foi assinado com
a criptografia desligada, o arquivo a ler é o .signed.pdf (etc.), e não um .enc.
Senha de criptografia perdida
Sintoma. O operador esqueceu a senha; existem saídas criptografadas que precisam ser lidas.
Realidade. Não há recuperação. O Bulk Signer não tem custódia de chaves, não tem recuperação, não tem endpoint de descriptografia. Com o salt e as iterações fixos, quebrar o PBKDF2 por força bruta com uma senha forte é computacionalmente inviável (e esse é o objetivo).
Para o futuro:
- Guarde a senha em um gerenciador de segredos que permita recuperá-la (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault).
- Imprima e lacre uma cópia em armazenamento físico, como backup de último recurso.
Integração com o Lacuna Signer
O passo a passo completo para o operador está em Integração com o Lacuna Signer. As entradas abaixo são os modos de falha específicos desse caminho.
Signer:Endpoint is required / Signer:ApiKey is required na inicialização
Sintoma. O bootstrap falha com uma exceção de validação sobre Signer:Endpoint ou Signer:ApiKey.
Causa raiz. Parte do bloco Signer:* está definida e o resto não. O validador é ativado pela presença
da seção: omita-a por inteiro e nada é exigido; escreva qualquer parte dela e o bloco inteiro é validado,
porque uma conexão configurada pela metade falharia no primeiro envio, em vez de falhar no boot.
Esta verificação não considera mais quais perfis existem — os perfis ficam no banco operacional e podem ser passados para o Lacuna Signer pelo dashboard a qualquer momento. A exigência passou para o perfil: veja a próxima entrada.
Correção. Defina tanto Signer__Endpoint quanto Signer__ApiKey (variáveis de ambiente), ou remova a
seção se este host assina tudo localmente. O formato da chave de API é application-id|secret.
Method = LacunaSigner, but this host has no Signer: settings
Sintoma. Uma recusa de boot citando o Method de um perfil, ou a mesma frase no formulário do perfil
quando um salvamento é recusado.
Causa raiz. Um perfil de assinatura seleciona o serviço remoto, e este host nunca foi informado de
onde ele está. A recusa é a mesma qualquer que seja a origem do perfil — uma entrada de
Signing:Profiles[] importada em um primeiro boot ou um salvamento pelas páginas de perfil do dashboard.
Correção. Defina Signer__Endpoint + Signer__ApiKey e reinicie, ou defina Method = Local no
perfil. A mesma recusa vale para Method = SignerFolder (2.16.0). Um perfil SignerFolder que já
estava armazenado quando as configurações sumiram não é recusado, e sim degradado — veja
abaixo.
Todo documento despachado falha com signer.unreachable
Sintoma. Os jobs chegam a Processing e passam imediatamente para Failed com o código de
auditoria signer.unreachable.
Causas possíveis:
- Chave de API errada. A chave de API em si é removida dos logs, mas um erro permanente do SDK com
status
401é o indício. Gere a chave de novo na administração do Lacuna Signer e atualize oSigner__ApiKey. - Rede inacessível. Execute
curl -v "$SIGNER_ENDPOINT/api/version"a partir do host. Se ocurlfalhar, corrija primeiro o firewall / proxy / DNS. - Erro de digitação no endpoint. O
Signer:Endpointprecisa incluir o esquema (https://). O banner de inicialização mostra o valor configurado — confira-o.
Documentos travados em AwaitingSigner além do Signer:TimeoutHours
Sintoma. O card Aguardando assinatura da página Painel sobe sem parar; nada passa para
Completed.
Causas possíveis:
- O participante não assinou. Abra a administração do Lacuna Signer e confira o status do documento
com o id correspondente. Se ele estiver
Pendingalém doSigner:TimeoutHours, o worker de polling vai fazer o job local falhar comsigner.timeoutno próximo ciclo — esse é o comportamento previsto. - O worker de polling não está rodando. Procure no log por
SignerPollWorker started. Se não aparecer, o host não tem configuraçõesSigner:*e, portanto, nem o gateway nem o worker de polling estão registrados — definaSigner__Endpoint+Signer__ApiKeye reinicie. (Desde a 2.1.0, o registro depende dessas configurações, e não dos perfis; assim, um host que as tem está pronto para um perfil passado para o Lacuna Signer depois de o host ter iniciado.) - O pipeline está pausado. O
GET /api/pipeline/stateretorna{ paused: true }. O worker de polling respeita a flag de pausa. FaçaPOST /api/pipeline/resumepara desbloquear.
O operador cancelou, mas o participante ainda vê o documento
Sintoma. O job está Canceled localmente; o participante que assina ainda recebe um e-mail de
lembrete ou vê o documento na caixa de entrada do Signer.
Causa raiz. No lado remoto, o cancelamento é feito em melhor esforço. Se a chamada de cancelamento
remoto falhou no momento do cancelamento local, a transição local foi feita, mas o documento remoto não
foi cancelado. O log registra uma linha de Warning sobre a falha do cancelamento em melhor esforço.
Correção. Cancele o documento manualmente na administração do Lacuna Signer. O job local está
corretamente Canceled e não precisa de mais nenhuma ação.
O dashboard não mostra o card "Aguardando assinatura" nem o painel "Lacuna Signer"
Sintoma. Um perfil está configurado com Method = LacunaSigner, mas a página Painel não mostra o
card Aguardando assinatura e a página Sistema não mostra o painel do Lacuna Signer.
Causa raiz. Desde a 2.1.0, os perfis ficam no banco operacional, e Signing:Profiles[] só é
importado no primeiro boot com a tabela de perfis vazia. Se você acrescentou ou alterou o perfil no
appsettings.Production.json depois desse primeiro boot, a edição não teve efeito — o banner de
inicialização avisa que a seção está sendo ignorada — e nenhum perfil armazenado tem
Method = LacunaSigner.
Correção. Confira a página Perfis de assinatura do dashboard, que mostra o que o banco realmente
guarda. Crie o perfil ali, ou passe um perfil existente para o Lacuna Signer (o host precisa das
configurações Signer:* — veja acima). Um perfil salvo chega ao host em execução em até um intervalo de
polling, sem reinicialização; recarregue a página Painel ou Sistema para ver o card e o painel.
O contador de erros transitórios sobe, mas nenhum job falha
Sintoma. O bulksigner_signer_api_errors_total{op="poll"} aumenta, mas os jobs continuam em
AwaitingSigner.
Causa. Isso é esperado em uma indisponibilidade breve. O contador de falhas por documento fica em
memória e tem como limite Signer:MaxConsecutiveApiFailures (padrão 5). Um polling bem-sucedido zera o
contador. Quando o contador de um único documento ultrapassa o limite, esse job falha com
signer.unreachable e sai de AwaitingSigner. As outras linhas não são afetadas.
Correção. Se a indisponibilidade do serviço remoto persistir, resolva isso primeiro. Uma reinicialização zera os contadores em memória; jobs que já falharam não recebem nova tentativa automática (a nova tentativa fica a cargo do operador).
Um 5xx, 408 ou 429, a página de erro de um proxy e o timeout do próprio cliente REST agora também
contam para o limite; até a 2.15.x eles eram tentados de novo a cada tique, sem contar, até o
Signer:TimeoutHours. Depois da atualização, uma indisponibilidade prolongada faz jobs AwaitingSigner
falharem com signer.unreachable mais cedo do que antes — tente-os de novo quando o Signer voltar.
Um job de pasta do Lacuna Signer termina Failed ou Canceled
Sintoma. Um job cujo objeto é um documento do Lacuna Signer (um perfil do método SignerFolder,
Integração com o Lacuna Signer)
termina sem chegar a Completed. Qualquer que seja o desfecho, o documento continua pendente no
Signer — este host nunca cancela nem recusa um documento que não criou —, então ele ainda pode ser
assinado depois que a causa for corrigida.
Canceled, sem código: No longer pending at Lacuna Signer: <status>. O documento seguiu adiante antes
de este host assiná-lo: foi cancelado, recusado ou expirou no Signer, a ação do fluxo do titular já foi
concluída, ou o Signer respondeu que a ação não está pendente
(NoPendingActionFoundForEmailAndIdentifier na URL de ação, SignatureNotPending ou
FlowActionNotPending no início) e uma segunda leitura do documento confirmou. Nada deu errado aqui. Se
o documento voltou a valer no Signer, use Tentar novamente no job; caso contrário, não há nada a
fazer.
Failed — leia o erro para ver o código, e o histórico do job para ver a frase:
| Código | O que aconteceu | Solução |
|---|---|---|
signer-folder.no-holder-action | O documento está na pasta vinculada, mas não tem nenhuma ação de assinatura para o titular do certificado — o titular está ausente, aparece só como aprovador, ou a ação dele não tem CPF no Signer. Registrado uma vez, na descoberta; nunca reivindicado. | Adicione o titular como signatário, com o CPF, no Lacuna Signer (a próxima varredura encontra a nova ação sozinha), ou tire o documento da pasta. Não aceita nova tentativa. |
signer-folder.signature-refused | O Signer recusou a assinatura na resposta, no início ou na conclusão. Os resultados da validação ficam no histórico do job — normalmente o certificado (revogado, expirado, não é o do participante). | Corrija o que o Signer apontou e use Tentar novamente. |
signer-folder.key-unavailable | A chave do perfil não assinou — um vault ou token que parou de responder, uma chave que não abriu. | Restabeleça o acesso à chave e use Tentar novamente. Um perfil cujo certificado nunca abriu dá profile.degraded. |
signer.unreachable | Não foi possível contatar o Signer, ou ele não respondeu de forma útil: uma conexão recusada, um timeout, um 5xx, 408 ou 429, ou uma resposta que não vem da API do Signer — a página de erro de um proxy, um corpo truncado. | Verifique o Signer:Endpoint, a rede e qualquer proxy no caminho, e use Tentar novamente. Se o histórico disser que a assinatura tinha sido entregue ao Signer, confira o documento lá antes. |
signer-folder.action-url-malformed | A URL de ação do Signer não trazia a chave do documento ou o ticket, que o início público exige: o Signer mudou o formato das URLs. | Atualize o Bulk Signer para uma versão que leia o novo formato; informe o problema se nenhuma ler. |
signer-folder.profile-changed | O perfil do job não assina mais em uma pasta do Lacuna Signer. | Volte o perfil ao método SignerFolder para usar Tentar novamente; a nova tentativa é recusada com este código até lá. |
signer-folder.interrupted | A execução que o assinava terminou depois do ponto em que a chave foi acionada — uma reinicialização, ou uma instância que parou de responder —, e a recuperação na inicialização ou a assunção perguntou ao Lacuna Signer: o Signer ainda informa a ação do titular como pendente, informa um estado que este host não interpreta como desfecho, ou não pôde ser consultado (sem configurações Signer:* neste host, sem resposta no tempo permitido, ou com erro). O histórico diz qual, e se a assinatura tinha sido entregue ao Signer. | Confira o documento no Lacuna Signer e use Tentar novamente — a nova tentativa o relê e termina Canceled se a ação do titular já estiver concluída; assim, ela nunca assina duas vezes. |
signer-folder.signature-failed | Qualquer outra coisa que este host consegue nomear: o documento saiu da pasta vinculada, a ação do fluxo deixou de ser do titular ou voltou a esperar uma etapa anterior, o Signer disse que a ação não estava pendente enquanto uma segunda leitura ainda a mostrava pendente (um participante que o Signer não associou — confira lá o CPF e o e-mail do titular), o Signer indicou um digest sob o qual este host não assina, ou o Signer respondeu com um erro próprio. | Leia a entrada do histórico; devolva o documento à pasta ou corrija o fluxo no Signer, e use Tentar novamente. |
Uma falha cujo histórico diz que a assinatura tinha sido entregue ao Lacuna Signer e pode ter sido
aceita é uma conclusão que nunca respondeu: confira o documento no Signer. Uma nova tentativa é segura
de qualquer forma — ela termina Canceled se a ação já estiver concluída. Uma que diz que a assinatura
tinha sido calculada, mas nunca foi entregue ao Lacuna Signer é uma gravação que este host não
conseguiu fazer no meio do caminho: o documento está intacto lá.
Um job que estava no meio da assinatura quando o host reiniciou, ou quando a instância dele parou de
responder, não simplesmente falha: ele é encerrado pela resposta do Lacuna Signer — Completed quando
a assinatura que ele entregou está lá, de volta à fila quando a chave nunca foi acionada, Canceled
quando o documento seguiu adiante antes de a assinatura sair do host, e Failed com
signer-folder.interrupted nos demais casos
(a tabela). Um Signer que não responde no
boot atrasa o boot em no máximo uma leitura de dez segundos, e nunca o impede.
Tentar novamente é o único caminho de volta. Uma nova tentativa é um job novo para o mesmo documento
e a mesma ação do fluxo, que relê o documento e recomeça do zero — uma nova URL de ação e um novo início,
cujo token é de uso único. Um documento encontrado de novo pela varredura nunca cria um segundo job
sozinho, qualquer que seja o status do primeiro. Uma nova tentativa é recusada com
job.already-processing enquanto outro job para a mesma ação do fluxo ainda está na fila ou em execução.
Um perfil de pasta do Signer está degradado dizendo que o host não tem configurações do Lacuna Signer
Sintoma. Um perfil do método SignerFolder mostra Este perfil não consegue assinar, com o motivo
this host has no Lacuna Signer settings (Signer:Endpoint and Signer:ApiKey), so it cannot look for
documents in the profile's Signer folder. O GET /api/profiles o informa como degraded: true, o
/api/ready traz uma linha signing-profile:<nome> vermelha que não conta para o veredito, e o banner de
inicialização o mostrou como DEGRADED. Nada na pasta do Signer dele é encontrado aqui; um job já
enfileirado para ele, ou uma nova tentativa, falha com profile.degraded. Todos os outros perfis —
inclusive os de assinatura local — continuam assinando.
Causa raiz. O perfil está armazenado com o método, e este host não tem configurações Signer:* — elas
foram removidas depois que o perfil foi salvo, ou, no modo cluster, esta instância está configurada de
forma diferente da que o salvou. Todo salvamento e o seed recusam o método em um host assim, mas um
perfil armazenado não é revalidado no boot, e o host sobe mesmo assim. Sem credencial para perguntar
qualquer coisa ao Signer, a varredura nem roda aqui; sem este aviso, o perfil simplesmente não faria
nada.
Correção. Defina Signer__Endpoint e Signer__ApiKey neste host e reinicie — as configurações
são lidas na inicialização, e a degradação some junto. Ou tire o perfil do método SignerFolder na
página dele, o que passa a valer no próximo job reivindicado.
Um perfil de pasta do Signer está degradado dizendo que o certificado não tem CPF
Sintoma. Um perfil do método SignerFolder mostra Este perfil não consegue assinar na página dele
e em /profiles, com o motivo its certificate carries no CPF, so no Lacuna Signer document can be
matched to the certificate's holder. O GET /api/profiles o informa como degraded: true, o
/api/ready traz uma linha signing-profile:<nome> vermelha sem fazer a resposta falhar, e o banner de
inicialização o mostrou como DEGRADED, se ele já estava assim na inicialização. Nenhum documento na
pasta do Signer dele vira job; um job já enfileirado para ele, ou uma nova tentativa, falha com
profile.degraded. Todos os outros perfis continuam assinando.
Causa raiz. O certificado abriu, mas não tem CPF — não é um e-CPF ou e-CNPJ ICP-Brasil, ou foi
emitido sem o CPF do titular. A ação do fluxo de um documento no Signer é associada pelo CPF do titular,
lido do certificado e nunca digitado; então nada no Signer poderia ser assinado com ele. O
/profiles/_new recusa um certificado assim de imediato; esta degradação vem pelos outros caminhos — um
perfil importado pelo seed, ou um perfil que passou da assinatura local para o método com o certificado
que já tinha.
Correção. Dê ao perfil um certificado e-CPF ou e-CNPJ no painel Certificado e reinicie — um
certificado é lido uma vez, na inicialização —, ou tire o perfil do método SignerFolder, o que passa a
valer no próximo job reivindicado. A varredura avisa que está ignorando o perfil uma vez, e não a
cada passada; nada se perde enquanto isso, já que os documentos continuam pendentes no Signer.
Um perfil de pasta do Signer está degradado dizendo que a pasta do Signer não existe
Sintoma. Um perfil do método SignerFolder mostra Este perfil não consegue assinar, com o motivo
Lacuna Signer answered that its Signer folder <id> ('<nome>') does not exist. O console e o log
durável disseram isso uma vez, na varredura que o descobriu; o GET /api/profiles o informa como
degraded: true, e o /api/ready traz uma linha signing-profile:<nome> vermelha que não conta para o
veredito. A varredura não cria job para ele, uma notificação para ele é descartada, e um job já
enfileirado para ele, ou uma nova tentativa, falha com profile.degraded. Todos os outros perfis
continuam assinando.
Causa raiz. A pasta foi excluída no Lacuna Signer (uma exclusão lá é permanente — o id nunca volta).
Nada na inicialização pergunta ao Signer sobre pastas: é a varredura que descobre isso, quando a listagem
de pendentes da pasta volta vazia e o Signer, perguntado se a pasta ainda existe, responde
FolderNotFound. Uma indisponibilidade não é isso: um Signer que não pode ser consultado —
inacessível, dando timeout, recusando a chave de API — não degrada nada. Essa varredura avisa could not
confirm that folder … still exists … The profile's state is unchanged by it a cada passada até o Signer
responder, o bulksigner_signer_folder_sweep_errors_total{op="folder"} sobe, e o perfil continua sendo
varrido.
Correção. Vincule o perfil a outra pasta no painel Certificado. Isso vale sem reinicialização: a
degradação pertence à pasta de que tratava e não acompanha o perfil para a nova. Ou tire o perfil do
método SignerFolder. Se a pasta existe no Signer, a resposta veio da organização errada: confira se
o Signer:Endpoint e a Signer:ApiKey apontam para a organização que a contém. A varredura pergunta de
novo a cada passada e desfaz a degradação sozinha quando o Signer encontra a pasta, avisando no console.
Cada instância descobre tudo isso por conta própria, pela própria varredura, e uma reinicialização
esquece a degradação até a primeira varredura seguinte descobri-la de novo — então, no modo cluster, uma
instância irmã pode informá-la um intervalo de varredura depois.
A linha signer-folder-sweep em /api/ready/details está vermelha
Sintoma. O /api/ready traz uma linha signer-folder-sweep com ok: false, e o veredito continua
ready: true. Em /api/ready/details, a linha diz N passes in a row at this instance have had a
failure, since …; the last: …. Documentos nas pastas do Signer vinculadas são criados no Signer e não são
assinados aqui, e o console avisou sobre a varredura a cada passada. O
bulksigner_signer_folder_sweep_errors_total sobe.
Causa raiz. Três ou mais passadas seguidas da varredura tiveram cada uma pelo menos uma falha. A
linha indica a última por etapa e tipo — listing the folder of profile '…', reading a document for
profile '…', the pass — e diz se o Lacuna Signer não pôde ser contatado ou não respondeu de forma
útil (uma indisponibilidade, um timeout, um 5xx, a página de um proxy), respondeu com um erro (uma
Signer:ApiKey revogada ou digitada errado, um endpoint que não é a API do Signer) ou este host
falhou (ao gravar o job, por exemplo). Ela nunca cita a mensagem do Signer, que pode conter a chave de
um documento; o log durável tem a exceção inteira, sob o aviso da própria etapa.
Ela não faz o veredito falhar de propósito: um 503 tiraria a instância do balanceador de carga, o que
não ajuda em nada o Signer e para todo o resto que este host faz. Monte o alerta sobre a linha, não sobre
o veredito.
Correção. Leia o log durável da etapa que a linha indica. Uma resposta de erro a cada passada é quase
sempre a credencial: confira a Signer:ApiKey e se o Signer:Endpoint aponta para a organização que
contém as pastas. Uma indisponibilidade se resolve sozinha — a linha fica verde na primeira passada sem
falha. Um único documento que falha a cada passada também mantém a linha vermelha: o log indica qual;
corrija-o no Signer ou tire-o da pasta vinculada. No modo cluster, cada instância informa a própria
varredura, e todas compartilham a credencial.
Documentos do Lacuna Signer só são encontrados no intervalo da varredura, e não em segundos
Sintoma. Documentos em uma pasta do Signer vinculada são assinados, mas minutos depois de criados — no
Signer:FolderSweepIntervalSeconds —, e não em segundos. O caminho das notificações não está
funcionando, e a varredura está fazendo todo o trabalho, que é para isso que ela existe: nada se perde,
só há latência (o webhook).
Leia a linha signer-notifications em /api/ready/details na instância que o Signer chama:
| A linha diz | Significado | Solução |
|---|---|---|
not accepted (Signer:WebhookSecret is not set) | A rota responde 404 signer-notification.not-configured. | Defina Signer__WebhookSecret (32 caracteres ou mais) e reinicie; depois, registre o webhook no Signer. |
accepted; none has arrived at this instance since it started | O Signer não está alcançando esta instância, ou está sendo recusado. | Confira se a URL do webhook nas integrações da organização no Signer termina em /api/signer/notifications e é o endereço público deste host; confira no log de entregas do Signer a resposta que ele recebeu. Um 401 (signer-notification.unauthorized, com bulksigner_signer_notifications_total{outcome="unauthorized"} subindo) significa que o token é diferente do Signer:WebhookSecret ou que a autenticação não é Bearer — X-API-Key é recusado. No modo cluster, leia a linha em todas as instâncias; cada uma informa só o que chegou a ela. |
accepted; the last arrived at this instance at … | As notificações chegam. | Se os documentos ainda esperam pela varredura, confira o bulksigner_signer_notifications_total: unreadable subindo significa que o Signer mudou o formato do corpo (o log durável diz por quê, e a varredura continua funcionando — informe o problema); ignored para um documento esperado significa que o corpo citou uma pasta a que nenhum perfil habilitado está vinculado. Um 503 (unavailable) significa que o banco operacional desta instância não está utilizável, ou que havia checagens demais esperando — o Signer tenta essas de novo. |
Rede / HTTPS
https redirect = on em uma instalação como serviço — os clientes não alcançam a API
Sintoma. O banner de resumo de prontidão mostra https redirect = on, a instalação está atrás de um
proxy reverso que faz a terminação TLS, e os clientes agora recebem 308 → https://localhost:8080/....
Causa raiz. Hosting:RequireHttps = true está definido em algum lugar, e o serviço está escutando
em HTTP puro; assim, o destino do redirecionamento aponta para uma porta que não serve HTTPS.
Correção. Defina Hosting:RequireHttps = false (o padrão do serviço), ou configure um certificado no
Kestrel e escute em HTTPS diretamente no processo.
Conflito na porta 8080
Sintoma. O bootstrap falha com
Failed to bind to address http://0.0.0.0:8080: address already in use.
Causa raiz. Outro serviço já está usando a porta 8080.
Correção. Mude o ASPNETCORE_URLS para uma porta livre (por exemplo, http://0.0.0.0:18080). Em
cada alvo:
| Alvo | Onde |
|---|---|
| Linux | Acrescente ASPNETCORE_URLS=http://0.0.0.0:18080 a /etc/bulksigner/bulksigner.env. |
| Windows | [Environment]::SetEnvironmentVariable("ASPNETCORE_URLS", "http://0.0.0.0:18080", "Machine") e reinicie. |
| Docker | Edite a linha ports: em deploy/docker/docker-compose.yml. |
Banco de dados
O dashboard congela enquanto um lote assina (SQL Server)
Sintoma. O dashboard trava, ou as páginas levam dezenas de segundos para carregar, mas só enquanto o pipeline está processando. Nenhum job falha.
Causa raiz. O READ_COMMITTED_SNAPSHOT está desligado no banco de dados. Sem ele, as leituras do
dashboard adquirem locks compartilhados e ficam bloqueadas atrás das gravações do pipeline. O Azure SQL o
habilita por padrão; o SQL Server on-premises, não.
Correção. O banner avisa no boot (store isolation = READ_COMMITTED_SNAPSHOT off …), e o console de
operação exibe um alerta. O Bulk Signer informa a situação e nunca executa o comando que a altera —
isso exige acesso exclusivo a um banco de dados que é seu:
ALTER DATABASE [BulkSigner] SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;
O WITH ROLLBACK IMMEDIATE encerra as outras conexões; por isso, pare o serviço primeiro. Depois,
reinicie-o e confirme que o banner não mostra mais a linha — com a opção ligada, nada é informado.
A linha do banco diz UNREACHABLE e o serviço subiu mesmo assim
Não é uma falha. Um banco de dados fora do ar durante uma janela de manutenção não pode transformar uma
reinicialização em indisponibilidade; por isso, o host inicia, a migração é pulada e o /api/ready
fica vermelho (o detalhe da verificação database, em /api/ready/details, indica o banco).
Correção. Corrija o banco e, depois, reinicie. O veredito de prontidão é calculado a cada requisição, mas também continua vermelho durante toda a vida de uma instância cujo boot pulou a migração — isso só volta ao normal no próximo boot, e não quando o banco volta.
Causas comuns: o banco de dados não existe (o Bulk Signer cria as tabelas, não o banco de dados); o
login não está mapeado para um usuário no banco; um TLS que o cliente não aceita (o Encrypt é True por
padrão, então um certificado de servidor não confiável faz o login falhar com certificate chain … not
trusted); ou, no Azure SQL acessado de dentro do Azure, apenas a porta TCP 1433 liberada, quando a
política de conexão Redirect também precisa das portas TCP 11000–11999.
O serviço se recusa a iniciar com Database migration failed
Causa raiz. Uma migração não pôde ser aplicada. Na maioria das vezes, o login não tem db_ddladmin,
necessário no primeiro boot e em qualquer boot após uma atualização que traga uma migração.
Correção. Conceda a role e reinicie. Esta falha é fatal por design — rodar com um schema que não corresponde ao código é pior do que não iniciar.
Um job ou uma página falha uma vez e depois funciona (SQL Server)
Não é uma falha. Com SqlServer, as novas tentativas em falhas transitórias ficam ligadas com os
padrões do EF Core — a tentativa inicial e até seis novas tentativas para os números de erro que o
cliente SQL classifica como transitórios, com cada espera limitada a 30 segundos. Elas ficam ligadas
porque rodar com o Azure SQL, na prática, exige isso, e de propósito não há chave de configuração: um
limite de novas tentativas que o operador pode ajustar é um limite que acaba ajustado para zero durante um
incidente.
Se as novas tentativas estão se esgotando, investigue o caminho de rede em vez do limite.
Comandos no banco estouram o tempo em 35 s alguns segundos depois de o App Service substituir o container
Sintoma. No Azure App Service com Database:Provider = SqlServer, de um a três comandos falham com
Execution Timeout Expired alguns segundos depois de a plataforma parar o container anterior no mesmo
worker — cada um levando 35 s, nunca 30 — e nada falha depois disso. As vítimas são as operações que
fizeram a requisição seguinte: Takeover sweep failed, Pipeline worker iteration failed, um ciclo de
heartbeat ou o carregamento de uma página. O /api/ready continua verde, e a verificação seguinte é
bem-sucedida.
Causa. Não é o banco. Quando a plataforma remove o container antigo, conexões que o novo container colocou no pool durante o aquecimento podem morrer na rede sem serem fechadas e continuar parecendo ativas para o pool de conexões. O primeiro comando em uma delas espera o timeout de comando inteiro, de 30 s, e depois mais 5 s para que o servidor confirme um cancelamento que nunca recebe — 35 s é a marca de uma conexão que parou de responder, enquanto uma consulta que o servidor está de fato bloqueando falha em 30 s. A conexão morta sai então do pool, e é por isso que o episódio termina sozinho; um timeout não gera nova tentativa, então cada conexão morta custa exatamente uma falha.
Correção. Nenhuma no produto: a varredura consulta de novo no próximo ciclo de polling, um ciclo de
heartbeat perdido está a 15 s do próximo, e o carregamento de uma página funciona ao recarregar. Quando a
janela importa, implante com parada — com a parada primeiro, o container antigo já não existe quando o
novo abre uma conexão (veja Alta disponibilidade). Não
aumente o timeout de comando nem acrescente uma nova tentativa para isso — o primeiro alonga a espera e a
segunda a repete. Um travamento real do banco tem outro aspecto: falhas em 30 s e sinais de DTU, deadlock
ou blocked_by_firewall no Azure Monitor.
A inicialização é recusada porque a connection string não corresponde ao provider
Causa raiz. Uma de duas recusas, conforme o Database:Provider:
- Com
SqlServer, um data source que aponta para um arquivo em vez de um servidor — o que acontece quando uma implantação troca o provider e esquece o caminho do SQLite. Sem a recusa, isso apareceria como uma falha de login em um servidor cujo nome é um caminho. - Com
Sqlite, uma connection string que aponta para um local do Azure Files — um arquivo de banco de dados acessado por SMB é a forma documentada de corrompê-lo.
Ainda com SqlServer: uma connection string ausente é recusada, em vez de o produto tentar adivinhá-la.
Nenhuma recusa exibe a string, porque ela pode conter uma senha; apenas o data source é citado.
ConnectionStrings:Default é uma única chave; portanto, não há como manter o servidor no
appsettings.Production.json e fornecer apenas a senha pelo ambiente. Um valor JSON deixado no lugar
junto com a variável de ambiente é ignorado sem aviso, em vez de ser combinado com ela.
Depois de migrar para o SQL Server, todo job e toda aprovação sumiram
Não é recuperável a partir do novo banco — e não é uma falha. Não há importador nem verificação no boot para um arquivo SQLite deixado para trás; assim, o novo banco sobe com um schema vazio: sem jobs, sem histórico, sem eventos operacionais e sem snapshots de aprovação nem aprovações registradas.
Correção. O antigo db/bulksigner.db ainda está em disco, a menos que algo o tenha removido.
Arquive-o e mantenha um cliente SQLite à mão para o dia em que alguém perguntar quem aprovou um arquivo de
pagamento anterior à migração. Veja em
Instalação a ordem certa de fazer
isso da próxima vez.
SQLite "database is locked"
Sintoma. Erros esporádicos mencionando "database is locked".
Causas possíveis:
- Um processo externo (por exemplo, uma ferramenta gráfica de SQLite) está com o banco aberto e mantém um lock de escrita.
- O sistema de arquivos não suporta locks (algumas montagens de rede).
Correção. Feche a ferramenta externa. Evite SQLite em montagem de rede — mantenha o banco em disco local.
O banco cresceu demais
Sintoma. O db/bulksigner.db tem vários gigabytes.
Diagnóstico. Confira a quantidade de linhas de histórico e de jobs. Não há retenção automática (veja Retenção).
Correção. Arquive o banco manualmente: pare o serviço, mova db/bulksigner.db para
db/bulksigner-archive-AAAAMM.db e inicie o serviço. Um banco novo é inicializado; o arquivo arquivado
fica somente leitura. Abra-o em um cliente SQLite para consultas históricas.
Com Database:Provider = SqlServer, o crescimento é o mesmo, mas o procedimento não: não há arquivo para
mover, e criar um banco novo manualmente descartaria as aprovações registradas junto com todo o resto.
Arquive linhas com as ferramentas do seu próprio SGBD.
O botão Limpar Jobs na página Sistema (ou DELETE /api/jobs) apaga todos os registros de job,
qualquer que seja o status (desde a 2.9.0), os arquivos que esses jobs deixaram — arquivos de entrada,
cópias preparadas, pastas de erro e saídas assinadas — e (desde a 2.10.0) todos os eventos operacionais
registrados antes dele. Nada é mantido para consulta posterior. Veja Operação.
Modo cluster
Tudo nesta seção exige Cluster:Enabled = true. Com a chave desligada, nada disso se aplica — veja
Azure App Service (modo cluster) para a implantação e
Alta disponibilidade para o que o modo oferece e o que não oferece.
Cluster mode refused to start, citando chaves de configuração
Sintoma. O host encerra no boot com uma mensagem que cita uma ou mais destas chaves:
Database:Provider, Storage:Provider, Storage:Inputs[] ou um Source de certificado.
Diagnóstico. São configurações que não teriam como funcionar, recusadas em vez de executadas pela metade. A mensagem cita todas as chaves com problema de uma vez, e não uma por tentativa; basta uma leitura:
| Chave citada | Valor exigido | Por quê |
|---|---|---|
Database:Provider | SqlServer | O banco é o ponto de coordenação do cluster; um arquivo SQLite não pode ser compartilhado entre hosts. |
Storage:Provider | AzureFiles | O lease do armazenamento local de arquivos não impede nada fora do próprio processo. |
cada entrada de Storage:Inputs[] | em AzureFiles | Uma pasta local de uma instância é invisível para as demais. A pasta default, criada automaticamente quando não há configuração, é local; por isso, uma primeira execução com a chave ligada também é recusada. |
um Source de certificado | nem Pkcs11, nem WindowsStore | Um token ou um repositório de máquina fica em uma única máquina, e as instâncias do cluster são intercambiáveis. Use Pfx (de preferência lido de um blob) ou AzureKeyVault. |
Correção. Corrija as chaves citadas ou desligue o Cluster:Enabled — desligado, o produto funciona
como instância única, sem mudanças. Um compartilhamento NFS do Azure Files é recusado explicitamente; o
compartilhamento de trabalho precisa ser SMB.
Boot recusado: uma identidade de instância could not be registered in 3 attempts
Sintoma. O host encerra citando a própria identidade de instância derivada, o log registra o mesmo em
Critical, e a mensagem diz que a identidade não pôde ser registrada — todas as gravações da linha de
heartbeat deste boot perderam a disputa para outra encarnação — e informa o último heartbeat e a versão de
quem venceu.
Até a 2.4.x, um boot que encontrava a própria identidade com um heartbeat ativo se recusava a iniciar (a 2.4.3 esperava antes). Desde a 2.5.0, ele desloca o detentor — veja a próxima entrada. A recusa acima é a única que restou.
Diagnóstico. Algo está reescrevendo a linha desta identidade mais rápido do que um boot consegue assumi-la: dois hosts com o mesmo nome iniciando no mesmo momento com um mesmo banco de dados, ou uma falha no banco. A identidade é o id de instância do App Service, quando a plataforma o define, e o nome da máquina, quando não define. A recusa é fatal de propósito: um boot que não consegue gravar a própria linha não pode ser distinguido de nenhum outro pela recuperação, pela assunção de jobs ou pela visão Instâncias.
Correção. Consulte a visão Instâncias na página Sistema de uma instância que está rodando, encontre a linha que detém essa identidade e pare o que mais a estiver usando, ou aponte-o para um banco de dados próprio. Não apague a linha para contornar a recusa enquanto o detentor ainda estiver rodando — isso remove o registro, não a condição.
Depois de uma reimplantação, a instância antiga registra has been displaced e se retira
Sintoma. Trocar a imagem de um app em execução (az webapp config container set) produz, no log, um
Warning displaced the previous incarnation … which was still live e, cerca de três segundos depois, um
Critical has been displaced. O /api/ready de um dos containers traz uma linha cluster-instance
vermelha (e continua retornando 200), a página Sistema dele mostra um aviso, e um evento operacional
InstanceStoodDown é registrado.
Diagnóstico. Esta é uma reimplantação in-place normal no App Service (desde a 2.5.0). A plataforma
inicia o novo container ao lado do antigo na mesma instância — os dois derivam a mesma identidade — e
mantém o antigo atendendo até que o novo passe no probe de aquecimento. O novo container assume a
identidade imediatamente e registra qual encarnação deslocou; o antigo percebe isso no heartbeat seguinte
e se retira: não reivindica jobs novos, não faz assunção de jobs, não faz polling no Lacuna Signer à
toa, conclui o que já detém e continua servindo a web até a plataforma pará-lo. O que ele deixar
inacabado é assumido um Cluster:StaleAfterSeconds depois do deslocamento, pela política comum de
assunção. Não há falha de inicialização nem ContainerStartupFailure; a visão Instâncias mostra a
encarnação deslocada abaixo da linha da sucessora. Veja
Operação.
Como ler o log da sobreposição. Enquanto os dois containers rodam, o App Service grava a saída dos dois processos em um só log, sem indicar de qual container veio cada linha; assim, o Critical do container antigo aparece entre as linhas de boot do novo e dá a impressão de que o novo container se retirou. Não se retirou: em um log de sobreposição, o Critical é sempre do container antigo — o recém-chegado registra um Warning citando a encarnação que deslocou.
Se, em seguida, o novo container falhar no aquecimento — uma imagem com problema, um erro de
configuração detectado depois do registro —, o App Service para o site inteiro, inclusive o container
antigo já retirado, e o reinicia com a imagem nova, em um laço de 503s. No App Service, a solução é
sua: aponte o app de volta para a tag anterior com az webapp config container set, e ele fica pronto de
novo em cerca de dois minutos. Parar o app antes de trocar a imagem e iniciá-lo depois (veja
Alta disponibilidade) evita totalmente a sobreposição e
continua sendo a forma mais limpa de implantar.
Uma duplicata real — dois hosts com o mesmo nome, ou um deployment slot com a connection string de produção — também não é mais recusada. O boot mais recente desloca o anterior, e uma reinicialização do host deslocado retoma a identidade; assim, os dois se alternam de forma ruidosa: um Warning em cada recém-chegado, um Critical em cada processo que se retira, e uma encarnação deslocada na visão Instâncias que não para de mudar. A cada momento, exatamente um deles reivindica trabalho. Encontre o host que não deveria estar usando essa identidade e renomeie-o ou aponte-o para um banco de dados próprio. Slots não são suportados nesta topologia.
Boot recusado citando dois bancos operacionais
Sintoma. O host encerra dizendo que o marcador do compartilhamento de trabalho indica um banco operacional diferente daquele configurado nesta instância, e cita os dois.
Diagnóstico. Dois clusters estão apontados para o mesmo compartilhamento de trabalho. Esta é a única catástrofe que nenhum banco de dados consegue detectar — cada banco acredita ser dono da árvore, e eles sobrescrevem os diretórios de preparação (staging), saída e erro um do outro —, e é exatamente para detectá-la que o marcador existe.
Correção. Decida qual banco é o oficial e reaponte ou desative o outro. Não apague o marcador para fazer a mensagem sumir; ele é a única verificação que detecta essa condição.
Ela só recusa com base em evidência, nunca na ausência dela; por isso, um compartilhamento que ainda não tem marcador e o instante em que o marcador está sendo gravado têm o risco reduzido, mas não eliminado — e uma verificação que roda uma única vez, no boot, não consegue ver um cluster rival que chegue depois. Essa verificação também não é o que impede que duas instâncias assinem o mesmo arquivo; isso cabe ao lease por arquivo e à reivindicação no banco. Veja Alta disponibilidade.
Operadores (ou aprovadores) voltam para a tela de login de forma intermitente
Sintoma. As sessões funcionam e depois deixam de funcionar, aparentemente ao acaso — e com mais frequência quanto mais instâncias estiverem rodando.
Diagnóstico. As instâncias não estão compartilhando um mesmo key ring de Data Protection; assim, um cookie emitido por uma é rejeitado pela seguinte. Os dois cookies de sessão usam esse key ring, então isso afeta tanto aprovadores quanto operadores. Duas causas:
- Um host tem
Cluster:Enabled = false. O local do key ring depende dessa chave; por isso, esse host ainda usa o diretóriokeys/local. - As instâncias estão apontadas para bancos operacionais diferentes. Banco diferente, key ring diferente.
Correção. Deixe a chave e a connection string idênticas em todas as instâncias — o que, no App Service, é automático, já que os app settings são definidos por app. Observe que a afinidade ARR não resolve isso: a afinidade serve ao circuito Blazor; o key ring compartilhado serve ao cookie.
A inicialização registra um Critical sobre instâncias em uma versão diferente da aplicação
Sintoma. Um Critical no boot citando heartbeats ativos com uma versão diferente, e o host inicia mesmo assim.
Diagnóstico. Versões diferentes estão compartilhando um mesmo banco, uma mesma fila e um mesmo compartilhamento de trabalho. É um aviso, e não uma recusa, de propósito: recusar impediria instâncias de iniciar durante todo o tempo que um heartbeat morto da versão antiga levasse para ser considerado obsoleto — que é exatamente o momento seguinte a uma implantação que falhou.
Correção. Termine a implantação — pare todas as instâncias, implante, inicie. Se não há nenhuma implantação em andamento, procure um slot ou uma segunda implantação usando este banco de dados. Trate o Critical como o alarme que ele é; nada mais vai impedir isso. (Um container antigo sendo deslocado durante uma reimplantação in-place não o dispara: trata-se da vida anterior da própria identidade, e não de outra instância.)
Um job está travado e nenhuma instância o processa
Sintoma. Uma linha fica em Processing, Verifying ou AwaitingSigner indefinidamente. Nenhum
evento de assunção aparece, e a recuperação no boot não a resolve.
Diagnóstico. A linha não tem dono, ou indica uma instância que não tem nenhuma linha de heartbeat.
A recuperação no boot trata apenas da identidade da própria instância, e a assunção depende do heartbeat
de um dono; assim, uma linha sem nenhum dos dois é uma linha que ninguém reconcilia. Linhas sem dono são
deixadas por uma build anterior à coluna de dono ou por uma execução com o modo desligado. Um job assim
despachado ao Lacuna Signer é pior do que parece: o Signer:TimeoutHours só é aplicado enquanto há
polling da linha; então, uma linha que ninguém consulta é uma linha sem limite de tempo.
Correção. Inicie uma vez com Cluster:Enabled = false e deixe a
recuperação na inicialização comum varrer todas as linhas em
andamento, qualquer que seja o dono; depois, religue o modo. Faça isso na atualização, antes do primeiro
boot em cluster, e o problema deixa de existir. Essa é a solução indicada por todas as telas que encontram
uma dessas linhas.
Linhas de log sobre reivindicações perdidas e conflitos de lease, em um cluster saudável
Sintoma. Linhas frequentes de "claim lost to a concurrent writer" e de conflito de lease de arquivos de entrada sempre que arquivos chegam em lotes.
Diagnóstico. Isso é o sistema funcionando. Todas as instâncias monitoram todas as pastas; por isso,
elas disputam cada arquivo que chega, e, no modo cluster, o lado perdedor é registrado no nível de
resultado esperado, com um id de evento próprio. Ainda assim, cada arquivo vira exatamente um job — o
enfileiramento perdedor é recusado por um índice único parcial e respondido como AlreadyActive.
Correção. Nenhuma. Nenhum dos dois resultados entra no limite de falhas consecutivas de uma pasta; assim, um cluster movimentado não consegue acionar o disjuntor por pasta só por estar movimentado. Veja Operação.
As séries do Prometheus pulam entre instâncias
Sintoma. Os gauges em /api/metrics são descontínuos, e o bulksigner_jobs_awaiting_signer mostra
um valor menor que a contagem do dashboard.
Diagnóstico. O /api/metrics é por processo, e o front door do App Service não consegue direcionar a
requisição para uma instância específica; assim, cada coleta cai na instância que o balanceador de carga
escolher. Nenhuma configuração restaura a continuidade da coleta. O gauge também é por instância, por
design: ele conta as linhas que esta instância acompanha por polling.
Correção. Use sum() sobre todas as instâncias para obter o total do cluster — nada é contado em
dobro, já que cada job tem exatamente um dono. Para um caminho suportado, use a distro do Application
Insights, que reconhece instâncias nativamente (Telemetria). Veja
Alta disponibilidade.
Uma pausa parou todas as instâncias, e isso não era esperado
Sintoma. O POST /api/pipeline/pause parou todas as instâncias, e não apenas aquela para a qual a
requisição foi enviada.
Diagnóstico. Não é uma falha. A flag de pausa é uma linha que todos os workers leem a cada iteração de polling; por isso, a pausa vale para o cluster inteiro — que é o que um operador quer dizer ao pausar "o pipeline". Não existe drenagem por instância, e ela não foi implementada de propósito.
Correção. Para tirar uma instância de serviço, pare-a e deixe a assunção reconciliar o trabalho dela. Observe que a assunção fica subordinada à pausa; assim, um cluster pausado não declara mortas as outras instâncias.
Específico do Docker
O docker compose ps mostra (unhealthy)
Sintoma. O container está rodando, mas aparece como (unhealthy).
Diagnóstico. Execute docker compose exec bulksigner curl -v http://localhost:8080/api/health de
dentro do container. A imagem base traz o curl; a linha HEALTHCHECK no Dockerfile é a versão oficial
do comando de verificação.
O chown -R 1654:1654 falha / divergência de propriedade de arquivos
Sintoma. Os logs do container mostram permissão negada em data/ ou logs/.
Causa raiz. A imagem roda como UID 1654. Em hosts Linux que fazem bind mount de ./data e ./logs,
esses diretórios precisam pertencer ao UID 1654.
Correção. Antes do primeiro start: sudo chown -R 1654:1654 ./data ./logs.
Uploads falham com Access to the path '/app/entrada' is denied
Sintoma. Um upload pelo dashboard ou pelo POST /api/files falha com
System.UnauthorizedAccessException: Access to the path '/app/<primeiro segmento de Storage:Inputs[0].Path>' is denied,
em um container cuja primeira pasta monitorada está em um compartilhamento do Azure Files. Arquivos
colocados no compartilhamento são pegos normalmente.
Causa raiz. Antes da 2.4.2, a pasta de destino dos uploads era derivada como um caminho local a
partir do Path da primeira pasta, qualquer que fosse o provider dela; assim, entrada/remessas no
compartilhamento virava /app/entrada/remessas no disco do container — o diretório de trabalho, que
pertence ao root. Em uma instalação como serviço do Windows ou systemd, a mesma falha passava mais
despercebida: o upload era aceito em uma pasta local que nenhum observador lê.
Correção. Atualize para a 2.4.2 ou posterior e reinicie. Não é preciso mudar a configuração: os uploads vão para a primeira pasta monitorada, no provider dela.
Específico do Windows
O serviço não inicia e não há entrada no log de Aplicativo
Sintoma. Start-Service LacunaBulkSigner falha; o Visualizador de Eventos não mostra nada útil.
Passos de diagnóstico:
- Execute o binário em modo console a partir do local de instalação:
cd "C:\Program Files\Lacuna\BulkSigner"; .\Lacuna.BulkSigner.exe. As exceções de bootstrap aparecem imediatamente. - Consulte
C:\ProgramData\Lacuna\BulkSigner\logs\bulksigner-*.log. - O log de Aplicativo registra apenas eventos no nível do serviço; os eventos da aplicação ficam no log em arquivo.
O serviço inicia, mas o arquivo de log está vazio
Sintoma. O Get-Service mostra Iniciado; o dashboard funciona; mas o bulksigner-yyyyMMdd.log está
vazio.
Causa raiz. A conta virtual do serviço não consegue gravar em
C:\ProgramData\Lacuna\BulkSigner\logs\. O script de instalação concede a permissão Modificar, mas uma
ACL alterada ou um software de segurança de terceiros pode ter desfeito isso.
Correção:
icacls "C:\ProgramData\Lacuna\BulkSigner" /grant "NT SERVICE\LacunaBulkSigner:(OI)(CI)M" /T
Específico do Linux
O systemctl status bulksigner mostra active (running), mas o /api/health não retorna nada
Sintoma. A unit está ativa, mas nenhuma resposta HTTP volta.
Diagnóstico. Execute journalctl -u bulksigner -f e procure o banner Service ready. Se o banner
nunca apareceu, o bootstrap está travado em alguma etapa. A unit com Type=notify só fica ativa quando o
bootstrap termina; portanto, se você vê active (running), o bootstrap terminou — confira se o
ASPNETCORE_URLS está definido corretamente no bulksigner.env.
O serviço está em estado failed após uma reinicialização do host
Sintoma. Após uma reinicialização do host, o systemctl status bulksigner está failed.
Diagnóstico. Execute journalctl -u bulksigner -b (desde este boot). Causas comuns:
- Uma variável de ambiente obrigatória não foi carregada — o
EnvironmentFileé opcional (-no início); assim, a unit inicia sem ele, e o validador falha em seguida. - O token PKCS#11 não estava conectado no boot. Conecte-o de novo e execute
sudo systemctl restart bulksigner.
Saída no console
Uma execução em primeiro plano mostra um terminal vazio / quase em branco
Sintoma. Uma execução em primeiro plano mostra o banner de boot e o resumo de Service ready, e depois o terminal parece silencioso — sem linhas de log por job, sem saída contínua.
Causa provável. Este é o comportamento previsto do Dashboard no console: em um terminal interativo, ele suprime a saída contínua do console e exibe um painel ao vivo, que se redesenha no lugar.
Diagnóstico.
- Redimensione o terminal / role para trás — o painel ao vivo pode estar algumas linhas abaixo da área visível.
- Confira
data/logs/bulksigner-*.log(ou oLogging:File:Pathque você configurou) — o log em arquivo está sempre ativo e captura tudo. - Verifique se o terminal suporta posicionamento de cursor. Terminais modernos funcionam; o
conhost.exelegado e alguns clientes SSH restritos passam a usar saída com rolagem. - Para desativar o painel e voltar à visão contínua do log, defina
Console:Dashboard:Enabled = falsee reinicie.
Implantações em modo serviço não estão recebendo nenhuma saída padrão
Sintoma. O journalctl -u bulksigner ou o docker logs <container> mostra o banner de bootstrap,
mas nenhum evento depois dele.
Causa provável. A regra de ativação do dashboard ao vivo deveria impedir que ele seja ativado em um
host de serviço. Se você suspeita que ela está sendo acionada indevidamente no seu host, force a
desativação: defina Console:Dashboard:Enabled = false no appsettings.Production.json e reinicie. A
saída contínua do console volta a aparecer.
Diagnóstico de último recurso
Quando nada acima ajudar:
- Aumente o nível de detalhe do log. Defina
Logging:File:MinimumLevel = "Debug"(ouVerbose) e reinicie. Reproduza o problema. Leia o log em arquivo. - Leia o banner de bootstrap. Ele mostra qual etapa estava mal configurada (impressão digital da licença, origem do certificado ou criptografia).
- Isole o problema por ambiente. Execute o mesmo binário em primeiro plano, em modo
Development— o terminal mostra o detalhe completo da exceção (o envelope de erro de Production o omite). - Leia o log de eventos operacionais. Desde a 2.13.0, a página Eventos do dashboard (e o
GET /api/events) lista todos os eventos operacionais — pausa e retomada, edições de perfil, decisões de aprovação, assunções de jobs, inícios e paradas do serviço — do mais recente para o mais antigo, com filtros por tipo, intervalo de datas e texto. Uma trilha que começa com um eventoJobsClearedcomeça ali porque o Limpar Jobs apagou o que havia antes. - Inspecione o banco de dados.
sqlite3 db/bulksigner.dbe consultas comoSELECT * FROM Jobs ORDER BY CreatedAt DESC LIMIT 20;dão um quadro completo da atividade recente.
Se, depois de tudo isso, o sintoma continuar sem explicação, entre em contato com o suporte da Lacuna Software com o banner de bootstrap, os trechos de log relevantes (a aplicação mascara segredos, mas confira antes de enviar) e os passos exatos para reproduzir o problema.
Anterior: Retenção. Voltar para: visão geral.