{
  "schemaVersion": 1,
  "produto": "Sentinela",
  "tipo": "Caso técnico com extratos sanitizados",
  "verificado_em": "2026-10-30",
  "autor": "Jeremias Cardoso",
  "relacao_comercial": "Sistema interno desenvolvido pelo autor do Fluxonar para o centro de operação em que trabalha; não é oferecido à venda.",
  "revisao_codigo": "2e51c8238902e555dd1273a3df0569862805fc57",
  "metodo": "Leitura do código e da documentação do repositório (somente leitura). Nenhum banco de produção foi consultado e o site do ONS não foi acessado; números de testes, pacotes e medições vêm dos registros do próprio repositório (os logs brutos não são versionados). Os trechos são literais, exceto as substituições entre colchetes.",
  "contagem": {
    "regra": "Arquivos versionados (git ls-files) no commit acima. Linha de código: linha não branca que não é só comentário (// e /* */ em Java e CSS; -- em SQL). aplicacao_java = src/main/java; estilos_css e migracoes_sql = src/main/resources; testes_java = src/test/java.",
    "categorias": {
      "aplicacao_java": {
        "arquivos": 195,
        "linhas_fisicas": 41378,
        "linhas_nao_brancas": 37731,
        "linhas_de_codigo": 29512
      },
      "estilos_css": {
        "arquivos": 3,
        "linhas_fisicas": 1088,
        "linhas_nao_brancas": 1048,
        "linhas_de_codigo": 932
      },
      "migracoes_sql": {
        "arquivos": 10,
        "linhas_fisicas": 232,
        "linhas_nao_brancas": 205,
        "linhas_de_codigo": 139
      },
      "testes_java": {
        "arquivos": 170,
        "linhas_fisicas": 35554,
        "linhas_nao_brancas": 30966,
        "linhas_de_codigo": 25633
      }
    },
    "decisoes_registradas": {
      "quantidade": 41,
      "primeira": "D-001",
      "ultima": "D-041"
    },
    "guardas_de_arquitetura": {
      "quantidade": 20,
      "classes": [
        "RegraAncoraDeAjudaTest.java",
        "RegraDaoIsoladoTest.java",
        "RegraDataFormatadaTest.java",
        "RegraDiagnosticoDeAjudaTest.java",
        "RegraDialogoPreparadoTest.java",
        "RegraEscritaDoIndiceTest.java",
        "RegraEstadoDeListaTest.java",
        "RegraEstadoUnicoTest.java",
        "RegraFronteiraDeEscritaTest.java",
        "RegraIdDeSecaoTest.java",
        "RegraLeituraDeFonteTest.java",
        "RegraMigracaoVerificavelTest.java",
        "RegraPdfEspelhaOHtmlTest.java",
        "RegraPisoDeFonteTest.java",
        "RegraResultadoDeColetaTest.java",
        "RegraRotuloDeRevisaoTest.java",
        "RegraSegredoEmpacotadoTest.java",
        "RegraSeloComPisoTest.java",
        "RegraSemChecagemDeDiscoNaTelaTest.java",
        "RegraTelemetriaSemSegredoTest.java"
      ]
    }
  },
  "substituicoes": [
    "[empresa]",
    "[pasta de dados de outro sistema]",
    "[portas]",
    "[dono]"
  ],
  "limites": [
    "Não é laudo independente de segurança, desempenho ou conformidade regulatória.",
    "O repositório não registra número de informes publicados, de cientes nem tempo economizado; o texto não afirma ganho de produtividade.",
    "O ganho de desempenho está provado em contagem de conexões, não em segundos medidos no centro de operação.",
    "O clone usado tem um commit só; as datas vêm dos documentos, não do histórico do git.",
    "Não se publicam nomes da empresa, de pessoas, de instalações, de estações, caminhos de rede nem detalhes de segurança da infraestrutura."
  ],
  "alegacoes": [
    {
      "id": "proposito",
      "alegacao": "Reduzir o trabalho manual de acompanhamento sem tirar a revisão e a aprovação humanas.",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        13,
        13
      ],
      "trecho": "- **Propósito durável:** reduzir o trabalho manual de acompanhamento dos Procedimentos de Rede sem retirar a revisão e a aprovação humanas do fluxo."
    },
    {
      "id": "o-que-faz",
      "alegacao": "Monitora revisões dos Procedimentos de Rede e do MPO, apoia a análise, publica informes e registra o ciente; a automação nunca publica sozinha.",
      "arquivo": "README.md",
      "linhas": [
        3,
        6
      ],
      "trecho": "**monitorar as\nrevisões dos Procedimentos de Rede e do MPO da ONS**, apoiar a análise das mudanças, publicar informes\ninternos e registrar o ciente da equipe. A revisão e a publicação são sempre humanas: a automação nunca\npublica sozinha."
    },
    {
      "id": "equipe",
      "alegacao": "Cerca de 15 operadores, 2 a 4 simultâneos, turnos de 24 h.",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        110,
        111
      ],
      "trecho": "- O Access não se sincroniza por OneDrive ou SharePoint: a sincronização perde escritas. O banco\n  fica na rede corporativa, usado por cerca de 15 operadores e 2 a 4 simultâneos, em turnos 24 h."
    },
    {
      "id": "versao-atual",
      "alegacao": "Versão 1.3.0.",
      "arquivo": "src/main/java/dev/sentinela/comum/AppInfo.java",
      "linhas": [
        9,
        9
      ],
      "trecho": "    public static final String VERSAO = \"1.3.0\";"
    },
    {
      "id": "suite-1256",
      "alegacao": "1.256 testes, 0 falhas, 0 erros na 1.3.0.",
      "arquivo": "estado/estado.json",
      "linhas": [
        15,
        15
      ],
      "trecho": "\"suite\": \"1256 testes, 0 falhas, 0 erros"
    },
    {
      "id": "tag-pendente",
      "alegacao": "A tag v1.3.0 não foi criada; depende do teste presencial.",
      "arquivo": "estado/estado.json",
      "linhas": [
        17,
        23
      ],
      "trecho": "  \"tag_pendente\": {\n   \"tag\": \"v1.3.0\",\n   \"commit\": \"1d300ac48b73d2ca0a03360574e575aeed626c7f\",\n   \"dono\": \"Jeremias\",\n   \"via\": \"DEP-7 apos o DEP-3 aprovado\",\n   \"criada\": false\n  },"
    },
    {
      "id": "envio-nunca-executado",
      "alegacao": "O envio automático do aviso nunca foi executado; a única prova é o teste presencial.",
      "arquivo": "estado/estado.json",
      "linhas": [
        30,
        30
      ],
      "trecho": "  \"limite\": \"O envio automatico do aviso NUNCA foi executado: nenhum teste dispara Outlook (por desenho) e o smoke aborta antes do login. A unica prova possivel e o DEP-3 presencial. A 1.2.0 nao foi tagueada nem publicada. O digest de arvore nao serve de ancora de release neste repositorio (muda com EOL e com arquivos ignorados); a ancora e o commit.\""
    },
    {
      "id": "tag-1110",
      "alegacao": "Tag v1.1.10 criada em 27/10/2026 22:37.",
      "arquivo": "estado/estado.json",
      "linhas": [
        27,
        27
      ],
      "trecho": "   \"tag\": \"v1.1.10 criada e empurrada em 2026-10-27 22:37 (objeto 3bb4dcd2e11183129d14f80abd1f5ce67b809688)\","
    },
    {
      "id": "interface-programatica",
      "alegacao": "A interface JavaFX é programática, sem FXML.",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        168,
        168
      ],
      "trecho": "- A interface JavaFX é programática; o projeto não usa arquivos FXML."
    },
    {
      "id": "java-embutido",
      "alegacao": "A estação não precisa de Java: o pacote portátil traz o runtime.",
      "arquivo": "README.md",
      "linhas": [
        23,
        24
      ],
      "trecho": "- **JDK 21** e **Maven** para compilar e testar. A estação do COT não precisa de Java: o pacote portátil\n  traz o runtime embutido."
    },
    {
      "id": "watchlist-1657",
      "alegacao": "A 1ª coleta trouxe 1.657 normas (1.207 de IO); o nome da empresa não aparece nos metadados; lista curada a partir do plano de contingência; as 1.657 ficam como histórico.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        54,
        57
      ],
      "trecho": "A 1ª coleta real trouxe **1.657 normas** (1.207 só de IO) — inviável e cheio de ruído para o COT da [empresa]. Filtrar automaticamente por \"[empresa]\" é impossível: o nome **não aparece em nenhum metadado da fonte** (0 ocorrências nas fixtures) — só estaria no conteúdo dos PDFs. Decisão:\n- **Watchlist curada (allowlist):** o COT cadastra a lista dos documentos que importam (origem: o **plano de contingência** que já mantêm). O coletor lista os metadados da fonte (JSON barato) e **só baixa/compara os que estão na watchlist** com revisão nova. Derruba a coleta de 1.657 → dezenas.\n- **Montagem da lista (as duas formas):** (a) marcar a partir do catálogo já coletado (busca → \"monitorar\"); (b) digitar/importar/colar códigos.\n- **As 1.657 já baixadas:** **mantidas** como histórico consultável no portal; o coletor passa a atualizar apenas a watchlist (não re-baixa)."
    },
    {
      "id": "watchlist-121",
      "alegacao": "121 procedimentos na lista do centro de operação.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        857,
        858
      ],
      "trecho": "A coleta manual não tinha como ser abortada. Uma rodada contra a watchlist real do COT percorre\n121 procedimentos baixando PDF; começá-la no horário errado significava esperar até o fim, e o"
    },
    {
      "id": "agendamento",
      "alegacao": "Mínimo de 2 dias por semana; padrão terça e quinta às 06:00.",
      "arquivo": "config.properties.example",
      "linhas": [
        181,
        186
      ],
      "trecho": "coleta.hora=06:00\n\n# Dias da semana em que a coleta roda, em CSV de nomes em ingles\n# (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY).\n# Minimo de 2 dias/semana (D-008). Padrao: TUESDAY,THURSDAY\ncoleta.dias=TUESDAY,THURSDAY"
    },
    {
      "id": "json-nao-layout",
      "alegacao": "Coleta pelos dados JSON porque o HTML não traz os documentos.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        36,
        38
      ],
      "trecho": "As duas páginas são **públicas** (HTTP 200, sem login) porém **renderizadas por JavaScript**: o HTML do servidor traz container vazio — Jsoup puro no HTML coleta ZERO documentos (armadilha: parece funcionar, retorna nada). Os dados vêm de endpoints REST do SharePoint, **abertos e testados com sucesso**:\n- **Vigentes:** `GET https://proxyportais.ons.org.br/ons.portalempregado.proxy/garproxy/_api/web/lists/getbytitle('ecm-documents')/items` (OData JSON com módulo, submódulo, revisão, vigência, arquivo). PDF via `garapi/api/processo/retornarpdf?url=...` — só aceita GET (HEAD=405); validar barato com header `Range`.\n- **MPO:** `GET https://www.ons.org.br/_api/web/lists/getbytitle('MPO')/items` com `Accept: application/json;odata=nometadata` (campos `MpoCodigo`, `MpoAssunto`, `MpoVigencia`, `OData__Revision`); paginação via `odata.nextLink`; fallback robusto: travessia por `GetFolderByServerRelativeUrl(...)/Folders|Files`. Ramo da página = `/MPO/Documento Normativo` (não misturar com Mensagem Operativa/Programados/Diagrama). PDF via URL direta do `FileRef` (HEAD 200 ok)."
    },
    {
      "id": "tres-sinais",
      "alegacao": "Sem novidade só quando revisão, vigência e hash coincidem.",
      "arquivo": "src/main/java/dev/sentinela/coletor/CicloColeta.java",
      "linhas": [
        651,
        676
      ],
      "trecho": "            if (versaoCanonica.isPresent()) {\n                VersaoDocumento canonica = versaoCanonica.get();\n                boolean mesmoHash = hash.equalsIgnoreCase(canonica.hashSha256());\n                boolean mesmaVigencia = mesmaVigencia(canonica, norma.vigenciaInicio());\n                if (mesmoHash && mesmaVigencia) {\n                    boolean ehUltimaCanonica = ultima\n                            .map(maisRecente -> maisRecente.id() == canonica.id())\n                            .orElse(false);\n                    if (ehUltimaCanonica) {\n                        boolean revisaoEstaNoIndice = indice.revisaoIndexada(norma.codigo())\n                                .map(indexada -> NumeroRevisao.normalizar(indexada)\n                                        .equalsIgnoreCase(revisaoNormalizada))\n                                .orElse(false);\n                        if (!revisaoEstaNoIndice) {\n                            List<String> paginasTexto = extrator.extrairPaginas(conferencia);\n                            indice.indexar(norma.codigo(), norma.titulo(), revisaoNormalizada,\n                                    paginasTexto);\n                            LOG.warn(\"{} rev {} ja era a ultima canonica, mas nao estava na \"\n                                            + \"revisao ativa do indice; {} pagina(s) foram \"\n                                            + \"reindexadas.\",\n                                    norma.codigo(), revisaoNormalizada, paginasTexto.size());\n                        }\n                    }\n                    LOG.debug(\"{} rev {} conferida por SHA-256: sem novidade.\",\n                            norma.codigo(), revisaoNormalizada);\n                    return Optional.empty();"
    },
    {
      "id": "bytes-canonicos",
      "alegacao": "Bytes aceitos de uma revisão nunca são sobrescritos; divergência falha fechada e a cópia fica separada.",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        39,
        41
      ],
      "trecho": "13. Bytes aceitos de uma revisão canônica nunca são sobrescritos. Republicação da mesma\n    revisão com hash ou vigência divergente falha fechada, preserva banco, índice e fluxos\n    dependentes e mantém a cópia recebida separada para investigação."
    },
    {
      "id": "diff-por-pagina",
      "alegacao": "Diff sobre o texto extraído página a página, com as páginas afetadas.",
      "arquivo": "src/main/java/dev/sentinela/analisador/DiffNormas.java",
      "linhas": [
        28,
        37
      ],
      "trecho": "    public Resultado comparar(List<String> paginasAntigas, List<String> paginasNovas) {\n        List<String> linhasAntigas = new ArrayList<>();\n        List<Integer> paginaDaLinhaAntiga = new ArrayList<>();\n        achatar(paginasAntigas, linhasAntigas, paginaDaLinhaAntiga);\n\n        List<String> linhasNovas = new ArrayList<>();\n        List<Integer> paginaDaLinhaNova = new ArrayList<>();\n        achatar(paginasNovas, linhasNovas, paginaDaLinhaNova);\n\n        Patch<String> patch = DiffUtils.diff(linhasAntigas, linhasNovas);"
    },
    {
      "id": "pacote-assistido",
      "alegacao": "O pacote leva prompt, controle de revisões e diff.",
      "arquivo": "src/main/java/dev/sentinela/analisador/ExportadorPacoteAssistido.java",
      "linhas": [
        16,
        17
      ],
      "trecho": "em analises_pendentes\\, com prompt + controle de\n * revisoes + diff."
    },
    {
      "id": "supervisor-publica",
      "alegacao": "Só uma pessoa com perfil SUPERVISOR publica.",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        20,
        20
      ],
      "trecho": "1. O informe só é publicado por ação explícita de uma pessoa com perfil `SUPERVISOR`; a automação nunca publica sozinha."
    },
    {
      "id": "ciente-append-only",
      "alegacao": "Ciente e auditoria são append-only.",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        23,
        23
      ],
      "trecho": "4. `ciente` e `log_auditoria` são append-only no contrato da aplicação."
    },
    {
      "id": "concorrencia-otimista",
      "alegacao": "Mutação de informe com comparação otimista; conflito nunca aparece como sucesso.",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        30,
        32
      ],
      "trecho": "10. Toda mutação de informe entre estações usa comparação otimista do snapshot exibido\n    (`id + status + revisão`), incrementa a revisão e deixa somente o vencedor auditar;\n    conflito concorrente nunca é apresentado como sucesso."
    },
    {
      "id": "vigia",
      "alegacao": "Vigia: mais de 24 h oferece recuperação; mais de 48 h, alerta vermelho para todos.",
      "arquivo": "src/main/java/dev/sentinela/portal/VigiaService.java",
      "linhas": [
        15,
        15
      ],
      "trecho": " *       &gt;24h oferece auto-recuperacao, &gt;48h e alerta vermelho para TODOS;</li>"
    },
    {
      "id": "d016-data",
      "alegacao": "Migração para um SQL na nuvem em 20/06/2026.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        104,
        104
      ],
      "trecho": "**Data:** 2026-06-20 · **Decisor:** [dono]"
    },
    {
      "id": "d017-data",
      "alegacao": "Nova decisão de banco em 24/06/2026.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        130,
        130
      ],
      "trecho": "**Data:** 2026-06-24 · **Decisor:** [dono]"
    },
    {
      "id": "d017-custo",
      "alegacao": "O banco na nuvem anterior foi cancelado por custo.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        131,
        131
      ],
      "trecho": "foi **cancelado por custo**"
    },
    {
      "id": "d018-data",
      "alegacao": "Volta ao Access em 19/07/2026.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        151,
        151
      ],
      "trecho": "**Data:** 2026-07-19 · **Decisor:** [dono]"
    },
    {
      "id": "d018-rede",
      "alegacao": "A rede corporativa barrou o banco remoto.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        153,
        153
      ],
      "trecho": "esbarrou na rede corporativa do COT"
    },
    {
      "id": "d018-validacao",
      "alegacao": "O caminho nunca pôde ser validado na estação real.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        153,
        153
      ],
      "trecho": "**nunca pôde ser validado na estação real**"
    },
    {
      "id": "d004",
      "alegacao": "Desenvolvimento fora da rede corporativa.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        25,
        25
      ],
      "trecho": "## D-004 — Ambiente de desenvolvimento = máquina particular (fora da rede corporativa)"
    },
    {
      "id": "access-concorrencia",
      "alegacao": "Medido em 01/08/2026: duas inserções em ~7 de 24 rodadas; 5 de 5 com zero linhas legíveis; UPDATE com guarda reportou 1 nas 3 estações em 4 de 4.",
      "arquivo": "estado/estado.json",
      "linhas": [
        439,
        439
      ],
      "trecho": "…AVISO MEDIDO EM 2026-08-01 (tarefa 5/SQL-02) - MUDA O ROTEIRO DO ACEITE: o Access NAO serializa escrita entre PROCESSOS. Medido com 3 JVMs separadas, largada no mesmo milissegundo, mesmo .accdb: (a) o indice unico nao e lock distribuido - em ~7 de 24 rodadas com 3 estacoes DUAS conseguiram inserir; (b) escrita concorrente de varios processos CORROMPE a tabela - lendo o arquivo direto com Jackcess, em 5 de 5 rodadas o .accdb ficou com rowCountMeta=1 e ZERO linhas legiveis (a linha do vencedor sumiu); com um unico escritor, 1 linha integra em 6 de 6; (c) UPDATE com guarda nao serve de arbitro entre estacoes - as 3 reportaram update=1 em 4 d…"
    },
    {
      "id": "d033-medicao",
      "alegacao": "Três JVMs no mesmo .accdb: em 2 de 8 rodadas duas inseriram; em outra bateria a tabela corrompeu.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        1228,
        1231
      ],
      "trecho": "Jackcess não a serializa. Medido em `SqlMutexAgenteConcorrenciaTest`: com três JVMs largando juntas\nsobre o mesmo `.accdb`, em 2 de 8 rodadas duas conseguiram inserir, e em outra bateria a tabela ficou\ncorrompida. Nenhum desenho de tabela conserta isso; o árbitro precisa estar fora do arquivo de dados.\n"
    },
    {
      "id": "d033-perdedor",
      "alegacao": "Antes da trava, o agente recusado seguia gravando e saía com código 0.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        1233,
        1235
      ],
      "trecho": "agendada existe em **cada** estação que aplicou o agendamento. Às 06:00 vários agentes disparam\njuntos. Antes desta decisão, o agente recusado por outra estação **seguia** para análise,\nimportação, IA e minutas, que gravam no `.accdb` compartilhado sem trava alguma, e saía com código 0."
    },
    {
      "id": "d033-trava",
      "alegacao": "Trava de arquivo do sistema operacional antes do mutex do banco; o agente inteiro roda sob ela.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        1239,
        1243
      ],
      "trecho": "1. **Trava de arquivo do sistema operacional** (`TravaDeColeta`, um `FileLock` em\n   `<nome-do-banco>.coleta.lock`, ao lado do `.accdb`), adquirida **antes** do mutex do banco. O SMB\n   a respeita entre máquinas. O arquivo **nunca é apagado** para destravar: a exclusividade é a trava\n   mantida pelo SO, não a existência do arquivo. Se o arquivo de coordenação não abre, a coleta não\n   entra (fail-closed)."
    },
    {
      "id": "d033-agente",
      "alegacao": "O agente recusado por outra estação não executa nada.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        1252,
        1254
      ],
      "trecho": "4. **O agente roda tudo sob a trava** (`RodadaDoAgente`): backup, ciclo de coleta e etapas\n   posteriores (análise, importação de respostas, IA, minutas). O agente recusado por outra estação\n   não executa nada e sai com código 0. Falha do ciclo ou de qualquer etapa sai com código 2 e deixa"
    },
    {
      "id": "ia-modo-assistido",
      "alegacao": "Análise opcional com IA; na rede do centro de operação o modo válido é ASSISTIDO e a IA externa é bloqueada.",
      "arquivo": "README.md",
      "linhas": [
        12,
        14
      ],
      "trecho": "  por uma tarefa agendada (`--agente`), e apenas **uma coleta por vez** entre as estações.\n- **Análise das mudanças** entre revisões, determinística e, opcionalmente, com IA. Na rede do COT o modo\n  válido é `ASSISTIDO` (a IA externa é bloqueada)."
    },
    {
      "id": "ia-adiada",
      "alegacao": "O modo ASSISTIDO é o caminho válido; a análise automática é recurso adiado.",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        229,
        231
      ],
      "trecho": "O modo `ASSISTIDO` é o\n  caminho válido e não depende de rede; a análise automática por IA permanece recurso adiado, não\n  trabalho pendente."
    },
    {
      "id": "ia-formato",
      "alegacao": "Resposta com resumo, relevância e trechos com página; sem evidência, \"nao encontrado\".",
      "arquivo": "prompts/analisador.md",
      "linhas": [
        9,
        15
      ],
      "trecho": "1. **Resumo objetivo** do que mudou nesta revisão (3 a 8 frases, linguagem direta de sala de operação).\n2. **Relevância para a [empresa]** (operação de transmissão): `ALTA`, `MEDIA` ou `BAIXA`, com justificativa curta.\n3. **Trechos impactados**: os pontos concretos que o operador precisa conhecer, cada um com o número da página.\n\n## Regras invioláveis\n\n- Use SOMENTE o material fornecido neste pacote. Sem evidência no material = escreva `\"nao encontrado\"`. NUNCA invente conteúdo, número de item ou página."
    },
    {
      "id": "ia-automatica",
      "alegacao": "A análise automática roda sobre o mesmo pacote; se falhar, o pacote volta para a via assistida.",
      "arquivo": "src/main/java/dev/sentinela/analisador/AnalisadorAutomatico.java",
      "linhas": [
        19,
        22
      ],
      "trecho": "/**\n * Analise automatica opcional (F3): roda o LLMProvider sobre os MESMOS pacotes\n * do modo assistido e importa pela MESMA validacao de esquema. Se a IA falhar,\n * o pacote continua la para a via assistida - nada se perde."
    },
    {
      "id": "ia-conteudo-publico",
      "alegacao": "Provedores externos de IA recebem só conteúdo público do ONS.",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        24,
        24
      ],
      "trecho": "5. Segredos ficam fora do Git; provedores externos de IA recebem somente conteúdo público da ONS."
    },
    {
      "id": "assistente-sem-ia",
      "alegacao": "O assistente de dúvidas não usa IA.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        321,
        321
      ],
      "trecho": "… revisou, e sempre diz de onde tirou. **Não há IA nesta fase** e isso honra a D-010: a IA não entra no caminho crítico de quem está em turno…"
    },
    {
      "id": "b002-backup",
      "alegacao": "O backup diário nascia e era apagado no mesmo ciclo pela rotação de 30 dias.",
      "arquivo": "docs/testador/ACHADOS.md",
      "linhas": [
        162,
        162
      ],
      "trecho": "### B-002 · 🔴 ALTA · Backup diário do `.accdb` nascia e era apagado no mesmo ciclo pela rotação de 30 dias — estação ficava sem backup nenhum, em silêncio — ✅ RESOLVIDO em 2026-07-22"
    },
    {
      "id": "b002-causa",
      "alegacao": "A cópia herda a data de modificação do banco e a rotação julgava a idade por ela.",
      "arquivo": "docs/testador/ACHADOS.md",
      "linhas": [
        170,
        175
      ],
      "trecho": "**Repro:**\n1. `.accdb` sem escrita há mais de 30 dias (carimbo do sistema de arquivos antigo).\n2. `BackupBanco.executarComResultado` copiava o banco para `sentinela_AAAA-MM-DD.accdb` com\n   `Files.copy`; no Windows, o `CopyFile` do SO preserva o carimbo de modificação da ORIGEM (o\n   banco) mesmo sem `COPY_ATTRIBUTES` — o backup recém-criado nascia, portanto, com a data do banco.\n3. A rotação de 30 dias julgava a idade pelo **carimbo do sistema de arquivos** — o backup recém-nascido"
    },
    {
      "id": "bom-incidente",
      "alegacao": "Config de teste gravado com BOM: a chave da pasta não foi reconhecida e o app caiu na base real.",
      "arquivo": "docs/testador/RELATORIO-2026-07-21.md",
      "linhas": [
        264,
        272
      ],
      "trecho": "Ao preparar o ambiente para validar o `.exe`, o `config.properties` de teste foi escrito por um comando\nPowerShell (`Out-File -Encoding utf8`) que grava BOM no início do arquivo. O BOM corrompeu a primeira\nchave do arquivo — o app não reconheceu `dados.raiz` — e o Sentinela caiu no padrão `[pasta de dados de outro sistema]`, que é a\nbase de **produção** real da estação, não uma base de teste isolada.\n\n**Consequências apuradas:**\n- O ciclo consultou **2 itens da watchlist no site real do ONS** (0 documentos baixados).\n- O registro do ciclo foi gravado no banco real (`log_coleta`).\n- O ciclo criou e apagou o backup do dia — o próprio bug **B-002** descrito acima, reproduzido ao vivo"
    },
    {
      "id": "bom-nada-perdido",
      "alegacao": "Nada foi perdido: banco real com o mesmo tamanho.",
      "arquivo": "docs/testador/RELATORIO-2026-07-21.md",
      "linhas": [
        276,
        276
      ],
      "trecho": "- `.accdb` de produção com o mesmo tamanho de antes: 2.875.392 bytes."
    },
    {
      "id": "bom-causa",
      "alegacao": "Causa raiz e orientação para não repetir.",
      "arquivo": "docs/testador/RELATORIO-2026-07-21.md",
      "linhas": [
        293,
        297
      ],
      "trecho": "**Causa raiz do incidente (para não se repetir):** `Out-File -Encoding utf8` no PowerShell grava BOM por\npadrão; qualquer preparo futuro de `config.properties` de teste deve evitar esse cmdlet/encoding para\nessa finalidade (ex.: `Set-Content -Encoding utf8` ainda grava BOM também — vale usar\n`[System.IO.File]::WriteAllText` sem BOM, ou conferir os bytes iniciais do arquivo antes de rodar o\n`.exe`)."
    },
    {
      "id": "b003-sintoma",
      "alegacao": "Na estação, o app abriu como instalação nova.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        216,
        216
      ],
      "trecho": "No teste do pacote portátil na estação do COT, o Jeremias abriu o Sentinela e viu uma **instalação nova**: sem documentos, sem usuários, pedindo troca da senha inicial. A leitura imediata foi a pior possível — *\"perdi todos os dados\"*."
    },
    {
      "id": "b003-causa",
      "alegacao": "A pasta padrão apontava para a pasta de dados de outro sistema, que existe nas estações.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        218,
        218
      ],
      "trecho": "A cadeia era esta. A chave `dados.raiz` não estava configurada; `AppConfig.dadosRaiz()` caía no padrão embutido `[pasta de dados de outro sistema]`; como essa pasta **existe** na estação (é a pasta de produção de outro sistema), `DataPaths.testarAcesso()` passou; `criarArvore()` plantou `[pasta de dados de outro sistema]\\sentinela\\`; o `MigrationRunner` chamou a fábrica de conexão, o UCanAccess **criou o `.accdb` vazio**, as três migrações rodaram, o `SeedAdmin` semeou admin e supervisor — e a tela de login apareceu como se fosse o primeiro uso. **Nenhum passo falhou.** Cada um fez exatamente o que estava escrito; o defeito era o encadeamento silencioso deles."
    },
    {
      "id": "b003-regra",
      "alegacao": "O boot nunca cria banco sem consentimento explícito.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        227,
        227
      ],
      "trecho": "**2. O boot nunca cria banco sem consentimento explícito — invariante, não recomendação.**"
    },
    {
      "id": "b003-testes",
      "alegacao": "Onze testes travam a invariante.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        232,
        232
      ],
      "trecho": "…te:** [`BootNaoCriaBancoSilenciosamenteTest`](../src/test/java/dev/sentinela/BootNaoCriaBancoSilenciosamenteTest.java), **11 testes** — `inicia…"
    },
    {
      "id": "d029-parar",
      "alegacao": "A primeira implementação de parar a coleta zeraria o relógio do Vigia; a guarda reprovou o build.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        887,
        893
      ],
      "trecho": "> **Esta decisão foi tomada duas vezes, e a primeira estava errada — fica registrada porque o erro\n> é instrutivo.** A primeira implementação criou um terceiro resultado no `log_coleta`,\n> `INTERROMPIDA`, com o raciocínio de que \"nem OK nem FALHA\" pedia um valor próprio: OK\n> reiniciaria o relógio do vigia por uma rodada incompleta, e FALHA ensinaria a ignorar o alarme.\n> As duas metades do raciocínio estavam certas; **a conclusão, não**. A guarda\n> `RegraResultadoDeColetaTest` reprovou o build e explicou o que faltava: *qualquer* linha nova no\n> `log_coleta` — com qualquer resultado — **reseta o relógio do eixo de atraso**. Uma rodada parada"
    },
    {
      "id": "d039-caminho",
      "alegacao": "Um operador gravou um caminho sem banco e não conseguia entrar; resetar a senha não adiantou.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        1564,
        1566
      ],
      "trecho": "Um operador gravou um caminho sem\nbanco e deixou de conseguir entrar; resetar a senha dele não adiantou, porque a senha está no banco\nverdadeiro, e a estação olhava para outro lugar."
    },
    {
      "id": "zip-antes-do-commit",
      "alegacao": "O ZIP da 1.1.10 foi gerado oito minutos antes do commit; proveniência não provada.",
      "arquivo": "docs/DIAGNOSTICO-DESEMPENHO.md",
      "linhas": [
        202,
        206
      ],
      "trecho": "- o ZIP foi gerado da árvore de trabalho **antes** do commit, às 15:34;\n- o conteúdo dessa árvore virou o commit `4f672d1` às 15:42:28;\n- portanto o artefato **não foi gerado a partir do commit**, e nenhuma tag o ancora. A proveniência\n  por construção continua **não provada**; a única coisa que se pode afirmar é que a árvore conferida\n  em 2026-10-27 (`git status --short` vazio) é a do commit."
    },
    {
      "id": "tag-1110-correcao",
      "alegacao": "A tag v1.1.10 foi criada em 27/10/2026 22:37.",
      "arquivo": "docs/DIAGNOSTICO-DESEMPENHO.md",
      "linhas": [
        145,
        147
      ],
      "trecho": "  `v1.1.10` **existe**, é anotada, foi criada e empurrada em **2026-10-27 22:37**, objeto\n  `3bb4dcd2e11183129d14f80abd1f5ce67b809688`, apontando para o commit\n  `4f672d1d376dd0b935662a1057aeaaec79f30d25`; `refs/tags/v1.1.10` está no `origin`. Nenhuma linha"
    },
    {
      "id": "dep3-reprova",
      "alegacao": "O teste presencial reprovou o aviso da 1.2.0 por três motivos; a 1.2.0 foi congelada.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        1733,
        1739
      ],
      "trecho": "quem publica confere e envia. Ela valia contra a 1.2.0 — e, em 2026-10-28, o `DEP-3` do Jeremias\ntestou exatamente esse aviso e o reprovou por três motivos. O primeiro é um defeito: os\ndestinatários saíam separados por `,`, e o Outlook não reconhece a vírgula como separador do campo\nPara — o e-mail chegou como um destinatário único inválido, ou não chegou. O segundo é a própria\ndecisão: a janela abria e **não enviava**, e o que foi pedido era o envio automático. O terceiro é\num pedido de conteúdo: o corpo deve levar a revisão das alterações num card HTML, não a frase fixa.\nCom o `DEP-3` reprovado, a 1.2.0 foi congelada sem tag e sem publicação."
    },
    {
      "id": "conexoes",
      "alegacao": "Conexões medidas em bases de 3 e 12 documentos: 4→13, 4→13, 7→25; depois 2, 2 e 3 constantes; 121 releituras a menos.",
      "arquivo": "estado/estado.json",
      "linhas": [
        1621,
        1621
      ],
      "trecho": "…MEDIDO com contador, em bases de 3 e 12 documentos: CienteService.pendentesPara 4->13 conexoes, InformeService.gerarMinutasAutomaticas 4->13, RelatorioAlteracoesService.listarAlterados 7->25; depois das correcoes, 2, 2 e 3 CONSTANTES. Mais 121 releituras a menos por ciclo de coleta. NAO FEITO DE PROPOSITO: remover a flag, que deixaria o handle Jac…"
    },
    {
      "id": "conexoes-ganho",
      "alegacao": "Ganho em segundos não medido; o provado é a contagem de conexões.",
      "arquivo": "estado/estado.json",
      "linhas": [
        1621,
        1621
      ],
      "trecho": "…GANHO EM SEGUNDOS NAO MEDIDO: o que esta provado e a contagem de conexoes, deterministica; o relogio sobre o share do COT nunca foi cronometrado.\","
    },
    {
      "id": "watchlist-363",
      "alegacao": "Importar a lista abria 3 conexões por código (363 para 121) e passou a abrir no máximo 4.",
      "arquivo": "estado/estado.json",
      "linhas": [
        1645,
        1645
      ],
      "trecho": "…6-09-30): a escrita da watchlist deixou de custar conexoes por codigo. MonitoramentoService.importar abria 3 conexoes por codigo (consulta, escrita, auditoria: 363 para os 121 do COT) e agora abre no maximo 4 (ler a lista, inserir em lote, re…"
    },
    {
      "id": "dep1-dep2",
      "alegacao": "A medição no centro de operação depende de implantar a 1.1.10 e de uma semana de coleta.",
      "arquivo": "docs/DIAGNOSTICO-DESEMPENHO.md",
      "linhas": [
        119,
        121
      ],
      "trecho": "- A primeira semana no COT depende de `DEP-1` (Jeremias implantar a 1.1.10) e `DEP-2` (tempo).\n  Sem dado real, o analisador devolve a prova local e diz que não há registro — nunca inventa\n  número."
    },
    {
      "id": "suite-111",
      "alegacao": "111 testes em 20/06/2026.",
      "arquivo": "docs/DECISOES.md",
      "linhas": [
        115,
        115
      ],
      "trecho": "111 testes verdes"
    },
    {
      "id": "suite-631",
      "alegacao": "631 testes em 28/07/2026.",
      "arquivo": "estado/estado.json",
      "linhas": [
        37,
        37
      ],
      "trecho": "…Suite final: 631 testes em 71 classes, verde, contada por duas …"
    },
    {
      "id": "suite-968",
      "alegacao": "968 testes na release 1.1.0.",
      "arquivo": "estado/estado.json",
      "linhas": [
        1583,
        1583
      ],
      "trecho": "… em 8ea0ca2, o pacote portatil foi gerado pelo script oficial e o ZIP esta em target/dist/Sentinela-1.1.0-portatil.zip. A suite integral anterior ao empacotamento fechou em 968/968 testes, 121 classes e zero falhas, erros ou ig…"
    },
    {
      "id": "suite-1145",
      "alegacao": "1.145 testes em 30/09/2026.",
      "arquivo": "estado/estado.json",
      "linhas": [
        1757,
        1757
      ],
      "trecho": "…suite completa 1145 testes, 0 falhas, 0 erros, 0 ignorad…"
    },
    {
      "id": "smoke-24",
      "alegacao": "Smoke com 24 verificações numa raiz nova e conferência do config real por SHA-256.",
      "arquivo": "docs/EMPACOTAMENTO.md",
      "linhas": [
        158,
        163
      ],
      "trecho": "Cada execução inventa uma estação: `APPDATA`, `LOCALAPPDATA` e raiz de dados próprios, sob\n`%TEMP%\\sentinela-smoke` (mude com `-Trabalho`). O `config.properties` da estação real é\nconferido por SHA-256 antes e depois; diferença **reprova** a rodada. As 24 verificações\ncobrem o runtime Java 21 embutido, as três recusas *fail-closed* do agente com o texto que\nelas devem ter, a criação do banco e as migrações 1..9 pelos JARs empacotados, e o `.accdb`\nbyte a byte idêntico entre duas validações seguidas."
    },
    {
      "id": "aceite-smb",
      "alegacao": "Aceite em três estações SMB: não executado.",
      "arquivo": "docs/ACEITE-SMB.md",
      "linhas": [
        138,
        149
      ],
      "trecho": "| SMB-01 | NÃO EXECUTADO | | |\n| SMB-02 | NÃO EXECUTADO | | |\n| SMB-03 | NÃO EXECUTADO | | |\n| SMB-04 | NÃO EXECUTADO | | |\n| SMB-05 | NÃO EXECUTADO | | |\n| SMB-06 | NÃO EXECUTADO | | |\n| TX-01 | NÃO EXECUTADO | | |\n| TX-02 | NÃO EXECUTADO | | |\n| CRED-01 | NÃO EXECUTADO | | |\n| VIG-01 | NÃO EXECUTADO | | |\n| MAIL-01 | NÃO EXECUTADO | | |\n| PERF-01 | NÃO EXECUTADO | | baseline de desempenho (TXT de cada estação) |"
    },
    {
      "id": "mapas-agosto",
      "alegacao": "Mapas de arquitetura gerados em 21/08/2026.",
      "arquivo": "docs/arquitetura/arquitetura-sentinela.json",
      "linhas": [
        5,
        5
      ],
      "trecho": "…\"gerado_em\": \"2026-08-21\","
    }
  ]
}
