Projeto integrador — uma fatia vertical de manutenção, ERP e IA segura
Este projeto integrador acompanha uma única jornada de manutenção da interface ao ERP e ao componente de IA. O leitor define contratos, implementa a mudança de estado de uma ordem, persiste dados com integridade, publica a integração de forma idempotente e limita a IA a uma recomendação que pode se abster. A atividade mostra onde autenticação, autorização por objeto, concorrência, auditoria, RAG e tool use se encontram sem transformar o exemplo em uma arquitetura de produção imaginária. Testes positivos, negativos e de falha fornecem a evidência final de que a fatia funciona e permanece recuperável.
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
technicianesupervisor; - 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 + auditoriaObserve 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:
CHECKrestringe estados válidos;version > 0protege integridade básica;- índices atendem consultas por responsável/status e site/data;
audit_eventssepara 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-seA 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 --noEmitOs 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:
- substituir o repository de memória por PostgreSQL, mantendo a interface e adicionando testes de integração;
- trocar token didático por OIDC em um BFF, mantendo
Actorcomo representação interna mínima; - empacotar a ponte Spring e validar o contrato contra ERP sandbox;
- substituir ranking lexical por recuperação real com ACL no índice e no banco;
- integrar um modelo com tracing, orçamento e evals, preservando abstenção;
- 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):
- somente OS
openpode iniciar; - técnico atribuído ou supervisor pode iniciar;
- outro site continua invisível;
- versão antiga retorna 409;
- a ação gera auditoria;
- 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:
- Explique a diferença entre autenticação, RBAC e autorização por objeto.
- Desenhe o caminho de uma reserva repetida e mostre onde a chave é verificada.
- Liste três razões para o token didático não ir a produção.
- Explique por que ACL deve ser aplicada antes de o trecho entrar no contexto.
- Diga o que os testes atuais não provam.
Fontes e relações
- OpenID Connect Core 1.0 — identidade sobre OAuth 2.0 e claims.
- OWASP API1:2023 — Broken Object Level Authorization — autorização em cada operação com identificador.
- OWASP API5:2023 — Broken Function Level Authorization — funções privilegiadas e negação por padrão.
- OWASP Top 10 for LLM Applications — riscos de prompt injection, divulgação e agência excessiva.
- PostgreSQL — Row Security Policies e CREATE POLICY — comportamento e sintaxe de RLS.
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.
Comprove o que você aprendeu
Responda todas as questões. O gabarito comentado só aparece depois do envio.