Projeto integrador — uma fatia vertical de manutenção, ERP e IA segura

O problema que estamos resolvendo

Um eletricista precisa consultar uma ordem de serviço, executar o trabalho e pedir sua conclusão. Se a ordem for crítica, outra pessoa deve aprová-la. Quando uma peça é necessária, o sistema reserva estoque no ERP sem duplicar a reserva durante uma repetição de rede. Um assistente pode explicar procedimentos e consultar o estado da OS, mas não pode revelar documentos nem ordens fora da permissão do usuário.

Esse caso parece pequeno, mas atravessa quase toda a pilha: interface, protocolo, regra de negócio, identidade, banco, integração, IA, observabilidade e segurança. É justamente por isso que ele é útil. Em vez de criar “todo o banco” e depois “toda a API”, construímos uma fatia vertical que produz uma evidência observável.

O laboratório executável está em examples/project-integrator. Leia primeiro o seu README.md; ele contém a árvore, comandos, matriz executável/exemplar e troubleshooting.

Pré-requisitos e resultado observável

Você deve saber o que são HTTP, JSON, função assíncrona e teste automatizado. Não é necessário ter PostgreSQL, Spring, ERP ou chave de modelo instalados. Ao final, você deve conseguir executar a suíte, abrir uma interface local, consultar os-1842 e explicar por que uma OS crítica passa por pending_approval.

Vocabulário essencial:

  • porta: contrato que descreve a capacidade necessária, sem fixar tecnologia;
  • adaptador: implementação da porta, como memória, PostgreSQL ou cliente ERP;
  • RBAC: autorização por papel, por exemplo technician e supervisor;
  • autorização por objeto: decisão se aquele ator pode acessar aquela OS;
  • idempotência: repetir a mesma intenção não repete o efeito;
  • concorrência otimista: uma escrita só ocorre se a versão ainda for a esperada;
  • ACL: lista/regra que limita quem pode recuperar um documento;
  • abstenção: o sistema declara que não possui evidência autorizada suficiente;
  • eval: caso de avaliação para comportamento probabilístico ou semântico;
  • correlation ID: identificador que conecta eventos da mesma solicitação.

Modelo mental: uma regra, várias fronteiras

sequenceDiagram
  actor T as Técnico
  participant W as Frontend
  participant A as API
  participant D as Domínio
  participant P as Repository
  participant E as ERP
  participant I as Assistente
  T->>W: consulta OS 1842
  W->>A: GET + credencial
  A->>A: autentica
  A->>D: ator + objeto + intenção
  D->>P: carrega dentro do escopo
  P-->>D: OS v4, crítica
  D-->>A: leitura autorizada
  A-->>W: 200
  T->>W: solicitar conclusão
  W->>A: POST + expectedVersion 4
  A->>D: requestCompletion
  D-->>A: pending_approval v5
  A->>P: salva + auditoria

Observe que TLS, token, papel e regra de domínio não são sinônimos. TLS protege o canal. O token fornece uma identidade alegada e assinada. O papel permite uma categoria de ação. A autorização por objeto confirma que o ator pode agir sobre os-1842. A máquina de estados decide se a transição faz sentido. Só a combinação protege o caso de uso.

Passo 1 — domínio antes do framework

domain/work-order.ts define estados e duas transições. requestCompletion confere três invariantes: a versão recebida continua atual; o ator é o responsável ou supervisor; o estado inicial é in_progress. Se a OS é crítica, o resultado é pending_approval; caso contrário, completed.

Essa regra não reside no botão nem no controller. Um aplicativo móvel, um job ou uma futura integração chamará a mesma regra. Também não aceitamos status vindo do cliente, pois isso permitiria pular estados.

Contraexemplo inseguro:

// INSEGURO: o cliente escolhe o resultado e a API apenas persiste.
await repository.save({ ...order, status: body.status });

No laboratório, a entrada é uma intenção: concluir ou aprovar. O servidor calcula o novo estado.

Passo 2 — autenticação não encerra autorização

auth/token.ts emite um token HMAC didático para testes. Ele confere assinatura com comparação em tempo constante, audiência, expiração e papéis permitidos. Isso ensina a fronteira, mas não é recomendação para inventar autenticação.

Em produção, use o provedor corporativo por OpenID Connect e uma biblioteca madura. Valide emissor, audiência, assinatura, validade e algoritmo conforme o perfil do provedor. ID token informa autenticação ao cliente; access token é a credencial destinada ao recurso protegido. Não aceite um token apenas porque “parece JWT”.

Depois da autenticação, canReadOrder considera o ator, seus papéis e os sites. O teste de BOLA troca o ID por uma OS real de outro site e exige 404. O objeto existe, mas não existe no espaço visível daquele ator. Isso reduz enumeração e mantém uma política uniforme.

Passo 3 — portas deixam a arquitetura testável

WorkOrderRepository oferece findById, save e list. O adapter em memória clona entradas e saídas para evitar que o teste altere o “banco” por referência. É rápido e determinístico, porém não prova SQL, transação, pool ou RLS.

A migração 001_work_orders.sql mostra a direção PostgreSQL:

  • CHECK restringe estados válidos;
  • version > 0 protege integridade básica;
  • índices atendem consultas por responsável/status e site/data;
  • audit_events separa trilha operacional;
  • Row-Level Security (RLS) limita linhas por site_id.

RLS é defesa em profundidade. A conexão precisa aplicar contexto com segurança, preferencialmente em uma transação, e testes devem usar papéis que não contornam a política. A aplicação ainda precisa de autorização, porque nem toda regra cabe em uma linha SQL e outros adaptadores também devem ser protegidos.

Passo 4 — a integração ERP precisa de semântica, não só HTTP

Uma queda pode ocorrer depois de o ERP reservar uma peça e antes de a API receber a resposta. O cliente repete a chamada. Sem idempotência, duas reservas surgem.

ErpReservationPort exige idempotencyKey. O adapter determinístico guarda a primeira resposta e devolve a mesma nas repetições; o teste comprova que o contador de chamadas efetivas permanece em um. Isso não é cache genérico: a chave identifica a mesma intenção de negócio.

ErpInventoryBridge.java concentra escopo, quantidade, chave e timeout. O SpringErpInventoryExample.java mostra como um controller poderia traduzir HTTP, mas está rotulado como contratual porque não há projeto Spring neste laboratório. No ambiente real faltam autenticação serviço-a-serviço, mTLS conforme risco, retry apenas para falhas elegíveis, circuit breaker, métricas e sandbox do ERP.

Passo 5 — RAG e ferramentas herdam as fronteiras de autorização

O assistente mínimo recebe blocos com allowedRoles. Primeiro filtra por acesso, depois ranqueia lexicalmente. Quando nada autorizado corresponde, ele se abstém. O objetivo não é simular embeddings; é tornar visível uma propriedade crítica: documentos proibidos não entram no contexto.

Se a pergunta contém uma OS, o assistente usa apenas a ferramenta get_work_order. Essa ferramenta chama a mesma porta e a mesma autorização do caso de uso. Não existe SQL arbitrário, fetch livre nem ferramenta que aceite um papel declarado pelo modelo.

pergunta → classificar risco → recuperar somente ACL permitida
        → selecionar ferramenta em allowlist → reautorizar o objeto
        → responder com citação OU abster-se

A expressão regular de prompt injection é apenas uma sentinela didática. Ataques indiretos podem estar em PDFs, páginas, e-mails ou saída de ferramentas e não conter palavras óbvias. O controle decisivo é limitar a autoridade: instrução externa não ganha privilégio; ferramenta recebe parâmetros validados; ação com efeito relevante exige política e, quando necessário, confirmação humana.

Passo 6 — observabilidade deve ajudar sem virar vazamento

Cada decisão grava horário, correlação, ator, ação, recurso e resultado. A função safeDetails usa allowlist de tipos e remove chaves sensíveis. “Logar tudo” é uma má prática: access tokens, cookies, senhas, prompt completo e documentos podem transformar a plataforma de logs em um banco de segredos.

Em produção, eventos precisam de retenção, acesso restrito, integridade, relógio confiável e alertas. Uma sequência de 404 para muitos IDs pode indicar enumeração; conflitos 409 repetidos podem mostrar cliente desatualizado; abstenção crescente pode sinalizar falha de indexação.

A suíte como argumento verificável

Execute:

node --experimental-strip-types --test `
  examples/project-integrator/domain/work-order.test.ts `
  examples/project-integrator/tests/*.test.ts
pnpm exec tsc --noEmit

Os testes estão separados por perguntas:

Grupo Pergunta
domínio transições e segregação de função estão corretas?
contrato OpenAPI e migração contêm as invariantes declaradas?
E2E a intenção atravessa HTTP→aplicação→domínio→repository?
segurança o controle falha fechado contra abuso conhecido?
eval o assistente recupera, cita, abstém e não vaza ACL?
smoke HTTP um processo real entrega frontend e API?

Um teste de string no SQL não prova PostgreSQL. Ele apenas impede que elementos educacionais desapareçam sem revisão. Para aceitar produção, execute a migração em banco efêmero, configure usuários, aplique SET LOCAL, verifique RLS com diferentes papéis, faça rollback e teste concorrência real.

Como evoluir sem criar um “big bang”

Use tracer bullets verticais:

  1. substituir o repository de memória por PostgreSQL, mantendo a interface e adicionando testes de integração;
  2. trocar token didático por OIDC em um BFF, mantendo Actor como representação interna mínima;
  3. empacotar a ponte Spring e validar o contrato contra ERP sandbox;
  4. substituir ranking lexical por recuperação real com ACL no índice e no banco;
  5. integrar um modelo com tracing, orçamento e evals, preservando abstenção;
  6. adicionar SLO, alerta, backup e restauração antes de chamar o serviço de pronto.

Cada etapa deve deixar a fatia anterior funcionando. “Migrar tudo e testar no fim” troca aprendizagem incremental por risco concentrado.

Erros frequentes e diagnóstico

“O usuário tem papel de técnico, então pode consultar /work-orders/:id.” Papel libera a função, não qualquer objeto. Aplique escopo na consulta e teste a troca do identificador.

“Retry resolve falha do ERP.” Retry sem idempotência pode duplicar efeito. Defina identidade da intenção, validade da chave e armazenamento do resultado.

“RLS substitui o backend.” Ela protege linhas no banco, mas não valida máquina de estados, ferramentas, ERP ou resposta do modelo.

“O modelo recusou no prompt, então a ferramenta é segura.” Prompts são probabilísticos. A ferramenta precisa de esquema estrito, allowlist, autorização e limites determinísticos.

“O teste em memória passou, então PostgreSQL funciona.” O teste prova domínio e porta, não SQL. Consulte a matriz “executável versus exemplar” no README.

Threat model e critérios de aceite

O README do laboratório traz a matriz completa ameaça→impacto→controle→teste. Os critérios essenciais são:

  • outro técnico/site não lê nem altera a OS;
  • OS crítica não é concluída sem papel independente;
  • versão antiga não sobrescreve estado atual;
  • repetição da mesma reserva não duplica estoque;
  • documento não autorizado nunca compõe a resposta;
  • ferramenta reaplica autorização por objeto;
  • ausência de evidência produz abstenção, não invenção;
  • auditoria é correlacionável e não contém credencial.

Aceite significa demonstrar cada propriedade em execução. Arquitetura desenhada, code review ou declaração do fornecedor são evidências auxiliares, não substitutos.

Exercício e evidência de competência

Implemente startWork(orderId, expectedVersion):

  1. somente OS open pode iniciar;
  2. técnico atribuído ou supervisor pode iniciar;
  3. outro site continua invisível;
  4. versão antiga retorna 409;
  5. a ação gera auditoria;
  6. atualize contrato, frontend e testes.

Antes de codar, escreva os casos positivo, negativo, ambíguo e concorrente. A evidência final é a suíte passando, uma chamada HTTP observável e uma explicação de qual camada rejeitou cada abuso.

Recuperação ativa:

  1. Explique a diferença entre autenticação, RBAC e autorização por objeto.
  2. Desenhe o caminho de uma reserva repetida e mostre onde a chave é verificada.
  3. Liste três razões para o token didático não ir a produção.
  4. Explique por que ACL deve ser aplicada antes de o trecho entrar no contexto.
  5. Diga o que os testes atuais não provam.

Fontes e relações

Relações: este projeto aplica o atlas de camadas, o modelo mental de cybersecurity, os livros de APIs, identidade, banco de dados, observabilidade, sistemas de IA e desenvolvimento assistido. Ele não os substitui; é o lugar onde as decisões se encontram e precisam funcionar juntas.

Teste de fixação

Comprove o que você aprendeu

Responda todas as questões. O gabarito comentado só aparece depois do envio.

1. Por que possuir o papel `technician` não basta para ler qualquer `/work-orders/:id`?
2. O ERP reservou uma peça, mas a resposta se perdeu e o cliente repetiu a chamada. O que evita duas reservas?
3. O RAG não encontra documento autorizado sobre a bomba Z. Qual comportamento é seguro?

Consulta universal

O que você quer encontrar?

Títulos, capítulos, conceitos, termos, laboratórios e ferramentas em uma única busca.

Digite pelo menos dois caracteres.