135 KiB
Planejamento da Reference Implementation (RI) — Enterprise MCP Service Bus
Gerado a partir do
README.md(Working Draft v0.2, 2026-09-03) na raiz do repositório, seguindo o processo de planejamento em 4 fases (extração → decisões técnicas → decomposição em backlog → autovalidação). Este documento planeja, não implementa: nenhum código de produção foi escrito.
8.1 Resumo da arquitetura compreendida
O README descreve uma reference architecture (não uma RI pronta) para um Enterprise MCP Service Bus: uma camada de integração corporativa acessível via MCP, em que o MCP Client (identidade autenticada de workload) define o teto máximo de privilégio (MaximumEntitlement), o MCP Gateway atua como PEP determinístico, e um PDP/Policy Engine avalia entitlement policy e transaction policy. O agente (LLM) nunca é raiz de confiança: informação por ele controlada pode restringir mas nunca ampliar autorização (EffectiveCapabilities ⊆ ClientEntitlement). Um Enterprise MCP Fabric publica/roteia/media capabilities entre domínios e backends corporativos existentes (API Gateway, ESB, service mesh, SaaS). Em torno desse núcleo de segurança, a v0.2 adiciona uma cadeia de personalização e orquestração governadas: Service owners + publishing pipeline alimentam o Global Capability Registry; Profile Intelligence produz uma visão de perfil genérica e substituível; o Entitlement Manager resolve cardápios MCP personalizados; o Offering Filter intersecta catálogo, entitlement, perfil e consentimento; o Service Orchestrator calcula a Next Best Action (NBA) sobre esse universo já governado; e o Agent Runtime despacha a NBA para agentes/canais. Toda decisão de relevância (perfil, ranking, NBA) é estritamente um subconjunto do que o entitlement já permite — nunca o contrário. O documento define 17 RFs, RNFs de segurança/disponibilidade/performance/escalabilidade/portabilidade, um modelo de risco (R0–R4), política de fail-closed, 10 testes de aceite de segurança e 18+ cenários de teste adversarial.
Componentes identificados: Agent · Agent Host/Orchestrator · MCP Client · MCP Gateway/PEP · PDP/Policy Engine · Global Capability Registry · Enterprise MCP Fabric · Service owners/Publishing pipeline · Entitlement Manager · Profile Intelligence · Offering Filter · Service Orchestrator · Agent Runtime/Channel adapters · Identity Provider · Audit/Telemetry.
8.2 Matriz de rastreabilidade
| Seção do README | Componente/Requisito | Tipo | Épico(s) |
|---|---|---|---|
| §6.1 | Agent | RF | EP-13 |
| §6.2 | Agent Host/Orchestrator | RF | EP-11, EP-12 (ver [LACUNA] G12) |
| §6.3, §18, §19 | MCP Client, identidade, shared-client rule | RF/RNF | EP-01 |
| §6.4, §9, §10, §21, §22 | MCP Gateway/PEP, tools/list, tools/call, anti-bypass, header routing | RF | EP-05 |
| §6.5, §25 | PDP/Policy Engine, entitlement+transaction policy | RF | EP-03 |
| §6.6, §23, §24 | Global Capability Registry, governance lifecycle, control/data plane | RF | EP-02 |
| §6.7, §32, §34 | Enterprise MCP Fabric, integração com plataformas existentes, topologia | RF | EP-06, EP-07 |
| §6.8 | Service owners, publishing pipeline, manifesto | RF | EP-02 |
| §6.9, §41 | Entitlement Manager, client profile | RF | EP-04 |
| §6.10 | Profile Intelligence, clusterização genérica | RF | EP-09 |
| §6.11, §7.3 | Offering Filter, conjuntos Global/Max/Filtered/Ranked | RF | EP-10 |
| §6.12 | Service Orchestrator, NBA | RF | EP-11 |
| §6.13 | Agent Runtime, channel adapters | RF | EP-12 |
| §7, §17 | Modelo de entitlement, risk tiers R0–R4 | RF | EP-02, EP-04 |
| §8, §11, §12 | Client-bound capability view, discovery vs execution, dynamic catalog rule | RF | EP-05 |
| §13 | Caching privado de tools/list | RNF | EP-05 |
| §14, §15, §29, §30 | Prompt injection, escalation vs abuse, cenários de ataque | RNF/Segurança | EP-15 |
| §16 | Modelo em camadas de segurança | RNF | EP-01, EP-03, EP-05, EP-14, EP-15 |
| §20 | Inbound vs outbound identity | RF | EP-07 |
| §26 | Human-in-the-loop | RF | EP-14 |
| §27 | Observabilidade e auditoria | RF | EP-08 |
| §28 | Threat model | RNF | EP-15 |
| §31 | Blast-radius containment | RNF | EP-04, EP-01 |
| §33, §43 | Comparação arquitetural, validação externa | Contexto | (sem tarefa — referência) |
| §35 | Anti-patterns | Restrição | EP-01, EP-05, EP-07, EP-15 |
| §36 (RF-01..17) | Requisitos funcionais | RF | todos |
| §37, §38 | RNFs, política de falha | RNF | EP-05, EP-14, EP-15 |
| §39, §40 | Naming/namespaces, versionamento | Restrição | EP-02 |
| §42 | Zero Trust (NIST) | RNF | EP-01, EP-03, EP-05 |
| §45, §46 | Critérios de aceite de segurança, testes adversariais | RF | EP-15 |
| §47 | Roadmap de adoção (Fases 0–5) | Contexto | Mapeado em Marcos (8.7) |
| §48 | Decisões abertas | — | Ver ADRs (8.4) e Lacunas (8.3) |
| §49 | ADRs sugeridos | — | Ver 8.4 |
| §50 | Axiomas | Restrição | Transversal a todos os épicos |
8.3 Lacunas e premissas
| ID | Tipo | Descrição | Impacto | Pergunta ao autor do README |
|---|---|---|---|---|
O README cita RI/ com "gateway MCP, PEP/PDP, OPA, Keycloak" na linha 10, mas o diretório não existia no repositório no início deste planejamento. Confirmado pelo autor em 2026-09-11: RI/ é a pasta-alvo onde a implementação será construída do zero — a frase no README é aspiracional/prospectiva, não uma referência a algo já existente. |
— (resolvido) | — | ||
| P1 | [PREMISSA CONFIRMADA] | OPA (PDP) e Keycloak (IdP/OAuth2-OIDC) são as tecnologias-alvo desta RI — citadas no próprio README e OSS/padrões abertos (seção 5 do superprompt). RI/ deve ser criado integralmente por EP-00 em diante (ver EP-00-T01, atualizado para deixar explícito que cria a pasta do zero). |
— | — |
O README não define stack de implementação. Confirmado pelo autor em 2026-09-11: adotar como referência concreta de camada Python/LangGraph/Langfuse o repositório de Christiano Hoshikawa, "Agent Platform OCI" (github.com/hoshikawa2/agent_platform_oci), analisado em detalhe (README + SPEC-002/003/004/007/012/018). |
— (resolvido) | — | ||
| P2 | [DECISÃO CONFIRMADA] | Python 3.12+/YAML/LangGraph/Langfuse conforme padrão do superprompt, com o padrão operacional concreto do Agent Platform OCI (Hoshikawa) como referência de implementação: StateGraph com router/supervisor por backend + Agent Gateway/Global Supervisor roteando entre backends; checkpointing plugável (memory/sqlite/mongodb/produção); taxonomia de eventos Langfuse IC/NOC/GRL (negócio/operacional/guardrail); instrumentação híbrida (spans automáticos por nó + _emit_ic/_emit_noc/_emit_grl manuais, fail-open); FastMCP como padrão de servidor MCP. Ressalva crítica adotada como ADR-022/023: o modelo de autorização do MCP Gateway daquele projeto (allowlist por agent_id declarado no payload de entrada) é o anti-padrão exato que nosso ADR-001 proíbe e não deve ser copiado — apenas a engenharia de proxy/cache/contratos é reaproveitável, nunca a decisão de autorização. Ver ADR-018, ADR-022, ADR-023. |
— | — |
| §48, pergunta 3: engine do PDP em aberto. Confirmado pelo autor em 2026-09-11: decisão técnica definitiva, não apenas premissa a validar. | — (resolvido) | — | ||
| P3 | [DECISÃO CONFIRMADA] | OPA/Rego é o PDP da RI — por ser OSS (Apache-2.0) e aderente a "padrões abertos" (seção 5 do superprompt); não é uma suposição a revisitar. Ver ADR-025 (numeração corrigida em 2026-09-11 para não colidir com o ADR-010 do README, "Global Capability Registry Governance"). | — | — |
| §48, pergunta 1: tecnologia de identidade do MCP Client em aberto. Confirmado pelo autor em 2026-09-11: Keycloak é decisão técnica definitiva, por ser OSS. | — (resolvido) | — | ||
| P4 | [DECISÃO CONFIRMADA] | OAuth 2.1 client_credentials via Keycloak é a identidade do MCP Client na RI (não apenas para dev/CI). mTLS/workload identity seguem como ponto de extensão documentado para produção, não implementado na RI. Ver ADR-019 (atualizado). |
— | — |
| G5 | [LACUNA] | §48, pergunta 5: modelo de downstream/outbound identity em aberto. | Médio | — |
| P5 | [PREMISSA] | Token exchange (RFC 8693) via Keycloak quando o backend suportar OIDC; senão, service account por adapter, documentado por backend em config/backends/*.yaml. Ver ADR-020. |
— | — |
| G6 | [LACUNA] | §48, pergunta 2: granularidade de client profiles em aberto. | Médio | — |
| P6 | [PREMISSA] | Granularidade = domínio + risk tier (ex.: Sales.Read=R1, Sales.Write=R2, Finance.Payments=R3), como já exemplificado em §7.1 e §17. |
— | — |
| G7 | [LACUNA] | Profile Intelligence "genérica e substituível" (§6.10) não define algoritmo, fonte de dados real nem taxonomia. | Médio | Existe um dataset de referência (sintético) que a organização gostaria de usar na RI, ou a RI deve ficar 100% sintética? |
| P7 | [PREMISSA] | RI implementa um stub baseado em regras determinísticas sobre atributos sintéticos, atrás de uma interface substituível (ProfileIntelligenceProvider), sem clusterização real nem taxonomia proprietária — cumpre o contrato, não a "inteligência". |
— | — |
| README não menciona LangGraph/Langfuse explicitamente. Resolvido em 2026-09-11 ao adotar o padrão operacional do Agent Platform OCI (Hoshikawa) — ver P2/ADR-022. | — (resolvido) | — | ||
| P8 | [DECISÃO CONFIRMADA] | LangGraph aplicado em duas camadas espelhando o padrão de referência: (a) EP-11 — StateGraph de jornada do Service Orchestrator, com nós fixos (resolve profile → resolve entitlement → filter offerings → decide NBA) e recálculo/degradação (§10.1) como aresta condicional; (b) EP-12-T03 — handoff entre agentes, inspirado no padrão de dois níveis do Hoshikawa (Global Supervisor entre backends + router/supervisor interno por backend), mas com a autorização de cada etapa sempre resolvida pelo nosso PEP/PDP, nunca por um campo autodeclarado no payload (essa é a lição central do ADR-023). Langfuse aplicado apenas onde há chamada real a LLM (EP-13), com instrumentação híbrida: spans automáticos por nó do grafo + eventos de negócio manuais (fail-open) na taxonomia IC/NOC/GRL (ver EP-08-T02 atualizado). Nenhum roteamento simples do Gateway/Fabric usa LangGraph. | — | — |
| G9 | [LACUNA] | §47 Fase 3 pede "multi-domain fabric" com federação; escopo de RI vs. produto não é definido. | Baixo | — |
| P9 | [PREMISSA] | RI demonstra 2 domínios (Sales, Finance) — suficiente para provar segmentação/blast-radius (§7, §31) sem federação completa. Federação fica fora de escopo (marcada could/stretch). |
— | — |
| G10 | [LACUNA] | §48 pergunta 9 (multi-cloud/híbrido) e pergunta 10 (dev/test/prod) não são endereçáveis em uma RI local. | Baixo | — |
| P10 | [PREMISSA] | RI roda 100% local via docker compose; separação de ambientes é demonstrada via overlay de configuração (config/env/{dev,ci}.yaml), sem infraestrutura cloud real. |
— | — |
| §6.2 ("Agent Host/Orchestrator") e §6.12 ("Service Orchestrator") descrevem responsabilidades sobrepostas. Resolvido em 2026-09-11 pelo posicionamento confirmado com o autor: §6.2 corresponde a uma plataforma multiagente externa e consumidora do bus (ex.: Agent Platform OCI/Hoshikawa — StateGraph + Global Supervisor), não a um componente que a RI precisa construir. | — (resolvido) | — | ||
| P11 | [DECISÃO CONFIRMADA] | §6.2 é o papel que uma plataforma de agentes externa (como o Agent Platform OCI) ocupa ao se conectar ao E-MCP-BUS como consumidora governada — ver ADR-024. Dentro da RI, apenas EP-11 (Service Orchestrator/NBA) e EP-12 (Agent Runtime/dispatch) são construídos, como a fronteira que essa plataforma externa atravessa até o MCP Client. Nenhum componente duplicado. | — | — |
8.4 ADRs
Formato: contexto → decisão → alternativas → consequências. ADR-001 a ADR-017 seguem os títulos sugeridos em §49 do README (decisões arquiteturais já implícitas nos princípios/axiomas); ADR-018+ são decisões de implementação da RI necessárias para fechar as lacunas da seção 8.3.
| ADR | Contexto | Decisão | Alternativas consideradas | Consequências |
|---|---|---|---|---|
| ADR-001 | Quem define o teto de privilégio do agente. | MCP Client (identidade autenticada) é a fronteira de MaximumEntitlement, nunca o agente/prompt. |
Autorização por role autodeclarado do agente. | Todo design de EP-01/EP-04 gira em torno da identidade do client, não do agente. |
| ADR-002 | Onde aplicar enforcement. | MCP Gateway é o único PEP; nunca delega decisão ALLOW/DENY ao LLM. | Enforcement distribuído em cada MCP Server. | Centraliza EP-05; servidores de domínio (EP-06) não reimplementam authz. |
| ADR-003 | tools/list filtrado é suficiente? |
Não — tools/call deve ser reautorizado independentemente do discovery. |
Confiar apenas em filtered discovery. | EP-05 implementa dois pontos de enforcement, não um. |
| ADR-004 | Contexto agêntico pode elevar privilégio? | Nunca — contexto só pode subtrair (Context may subtract, must never add). |
Permitir elevação mediante "confiança" do modelo. | Testes de EP-15 (Teste 3/4) validam a invariante. |
| ADR-005 | Cache de tools/list entre clients. |
cacheScope=private, chave = identidade+contexto+policy_version. |
Cache compartilhado por performance. | EP-05-T03; teste dedicado de isolamento (EP-15). |
| ADR-006 | Token do agente pode ser repassado ao backend? | Não — proibido blind token passthrough; inbound ≠ outbound identity. | Passthrough direto do token do client. | EP-07 implementa exchange/service account por backend. |
| ADR-007 | Backend acessível diretamente pelo client? | Não — rede/identidade restringem acesso só ao Gateway/Fabric. | Expor Fabric publicamente com authz "best effort". | EP-05-T07, EP-15 (teste de bypass). |
| ADR-008 | Como segmentar clients. | Por domínio + risk tier (R0–R4), não um client global. | Um client "enterprise admin" único. | Define P6 (granularidade) e EP-04. |
| ADR-009 | Como classificar capabilities. | Risk tiers R0–R4 no Registry, controlando aprovação/observabilidade exigidas. | Sem classificação formal. | EP-02-T01 inclui risk_tier obrigatório no schema do manifesto. |
| ADR-010 | Governança do Global Capability Registry (título e número herdados de §49 do README — "Global Capability Registry Governance"). | O Registry é um control-plane asset (§6.6) com autoridade e ciclo de vida próprios, separados da visão de catálogo entregue a cada client. Na RI: armazenamento e API de leitura dedicados (EP-02-T03), consumidos por Gateway/Fabric/Offering Filter mas nunca editados por eles; lifecycle formal nominate→review→register→publish→observe→(change|deprecate→retire) (§23) implementado como máquina de estados (EP-02-T04); toda alteração passa pelo publishing pipeline (EP-02-T02) — nenhum cadastro manual direto em produção (RF-11). Owner organizacional do Registry (quem aprova mudanças de schema/política do próprio control plane) permanece uma decisão de governança corporativa fora do escopo técnico da RI (§48, pergunta 4, segue aberta). | Deixar o Registry como uma tabela editável ad hoc por qualquer serviço; fundir a visão de controle (Registry) com a visão de execução (catálogo filtrado por client), o que violaria a separação control plane/data plane do §24. | EP-02 inteiro; reforça que EP-05 (Gateway) e EP-10 (Offering Filter) só leem o Registry, nunca escrevem nele. |
| ADR-011 | Ciclo de publicação de capabilities. | Pipeline obrigatório com gates de schema/risco/policy/health antes de entrar no Registry — sem cadastro manual em produção. | Registro direto via API administrativa sem pipeline. | EP-02-T02 implementa o gate; nenhuma capability "nasce" já publicada. |
| ADR-012 | Autoridade do cardápio máximo. | Entitlement Manager é a única autoridade de MaximumEntitlement; não faz ranking nem confia em atributos autodeclarados do LLM. |
Fundir Entitlement Manager e Offering Filter num único componente. | Mantém EP-04 e EP-10 como serviços/módulos distintos com contratos próprios. |
| ADR-013 | Contrato de Profile Intelligence. | Interface genérica e substituível (ProfileView versionado, com reasonCodes), implementação plugável. |
Acoplar a um vendor/taxonomia específica de segmentação. | EP-09 define apenas o contrato + stub de referência (ver P7). |
| ADR-014 | Perfil pode ampliar entitlement? | Nunca — FilteredOfferings ⊆ MaximumEntitlement, perfil só ordena/reduz. |
Perfil de alto engajamento desbloqueando novas tools. | EP-10-T02 traz teste de propriedade validando a invariante para entradas aleatórias. |
| ADR-015 | NBA é decisão de autorização? | Não — é intenção de orquestração; Gateway/PDP reautorizam toda execução. | Orchestrator despachar direto para o backend sem passar pelo Gateway. | EP-11-T04 implementa o branch de DENY/recálculo do §10.1. |
| ADR-016 | Contrato de dispatch/handoff do Agent Runtime. | Objeto de dispatch versionado (decisionId, expiresAt, reasonCodes) que não substitui o token do MCP Client. |
Runtime injetar credenciais próprias no agente. | EP-12-T01 consome exclusivamente o dispatch object + MCP Client já existente. |
| ADR-017 | Correlação ponta a ponta. | profileVersion+entitlementVersion+decisionId+policyDecisionId+mcpRequestId propagados via OTel/trace context em todos os componentes. |
Correlação apenas por log textual sem padrão. | EP-08-T03 formaliza os campos; base para EP-15 (auditabilidade). |
| ADR-018 | Stack de implementação da RI (fecha G2). | Python 3.12+, uv, Pydantic v2, YAML declarativo com JSON Schema, SDK oficial mcp. |
Node/TypeScript (SDK MCP também oficial); Go. | Define toda a estrutura de repositório (8.5). |
| ADR-019 | Identidade do MCP Client na RI (fecha G4 — decisão confirmada pelo autor em 2026-09-11). | Keycloak (OAuth 2.1 client_credentials) é a identidade do MCP Client na RI, por ser OSS. Decisão definitiva; mTLS/workload identity seguem documentados como extensão de produção não implementada na RI. |
mTLS completo desde o início. | Reduz esforço de EP-01 sem violar §19 (propriedades desejáveis ainda são satisfeitas: curta duração, revogável, audience-bound). |
| ADR-020 | Downstream identity na RI (fecha G5/P5). | Token exchange (RFC 8693) quando backend suporta OIDC; senão service account isolado por adapter. | Reutilizar sempre o token do client (proibido por ADR-006). | EP-07; documentado por backend em manifesto. |
| ADR-021 | Escopo de LangGraph/Langfuse (fecha G8/P8). | LangGraph somente no Service Orchestrator (EP-11) e handoff do Agent Runtime (EP-12); Langfuse somente onde há chamada real a LLM (EP-13). Checkpointer: SQLite em dev. | Usar LangGraph também no Gateway/Fabric para "consistência de stack". | Evita over-engineering vetado pela seção 6 do superprompt; Gateway/Fabric continuam Python puro. |
| ADR-022 | Referência concreta de implementação Python/LangGraph/Langfuse (fecha G2 — decisão confirmada pelo autor em 2026-09-11). | Adotar como referência operacional o "Agent Platform OCI" de Christiano Hoshikawa (github.com/hoshikawa2/agent_platform_oci): StateGraph com router/supervisor por backend + camada superior de roteamento entre backends (nosso EP-11/EP-12); checkpointing plugável (memory/sqlite/mongodb/produção — nós adotamos SQLite em dev, ver ADR-021); taxonomia de observabilidade IC/NOC/GRL via Langfuse (evento de negócio/operacional/guardrail), com instrumentação híbrida (span automático por nó + emissão manual fail-open); identity.yaml/BusinessContext para normalizar identidade de negócio entre canais; FastMCP como padrão de servidor MCP. Análise completa registrada em docs/research/hoshikawa-agent-platform-oci.md. |
Continuar com o padrão genérico da seção 6 do superprompt sem referência concreta de terceiros (mais lento, mais decisões ad hoc); adotar um framework de agentes de mercado (CrewAI, AutoGen) em vez de LangGraph puro. | Muda a forma (não a segurança) de EP-08, EP-11, EP-12, EP-13 — ver ADR-023 para o limite explícito do que NÃO é adotado. |
| ADR-023 | Autorização de tools NUNCA vem de um campo declarado no payload/conversa (achado crítico da análise do Hoshikawa, reforça ADR-001/002/003). | O MCP Gateway do Agent Platform OCI autoriza tools por um allowlist estático (tools.yaml/authorization.agents.<agent_id>.allowed_tools) avaliado sobre agent_id, campo que chega como dado do payload de entrada da conversa, não como claim de uma identidade autenticada verificada independentemente — a própria SPEC-004 deles admite que isso não substitui autenticação/autorização real. Nossa RI reaproveita a forma de proxy desse MCP Gateway (contratos ToolInvocation/ToolResult, cache por tool idempotente, catálogo /v1/tools) exclusivamente na camada de Fabric (EP-06)/UX conversacional, mas a decisão ALLOW/DENY continua 100% no nosso PEP (EP-05) consultando o PDP (EP-03) com a identidade autenticada do MCP Client (EP-01) — nunca com agent_id/tenant_id autodeclarado no payload. |
Reaproveitar também o bloco authorization deles como atalho de implementação. |
EP-15-T03 ganha um cenário adversarial explícito de "payload-declared agent_id spoofing" (variação do Teste 4, §45) para provar que esse vetor específico está fechado na nossa RI. |
| ADR-024 | Posicionamento do E-MCP-BUS em relação a plataformas de orquestração multiagente externas, como o Agent Platform OCI (decisão confirmada e ampliada pelo autor em 2026-09-11 — resolve G11). | O E-MCP-BUS não compete com plataformas de orquestração como o Agent Platform OCI — mas também não se limita a coexistir sob elas. Princípio de design explícito: modularidade + compatibilidade = escolha. Nosso Service Orchestrator (EP-11) não é um passa-through fino: ele calcula NBA e NBO (Next Best Action/Next Best Offer) com um modelo de decisão próprio (regras + guardrails +, opcionalmente, um modelo de IA de ranking/recomendação — ver EP-11-T03) sobre o universo de FilteredOfferings que o MCP Fabric expõe (serviços, ofertas, ações, campanhas — não só "tools" técnicas). Isso habilita dois modos de integração igualmente válidos, escolhidos pelo adotante conforme a topologia desejada: (A) Governança-somente — uma plataforma de orquestração externa (ex.: Agent Platform OCI, com seu próprio Global Supervisor e StateGraph decidindo NBA) conecta-se ao E-MCP-BUS apenas para acesso governado ao Fabric; nosso Service Orchestrator não participa, ou participa apenas como fonte de FilteredOfferings (como já descrito no restante deste ADR e em EP-13-T05). (B) Substituição completa — nosso próprio Service Orchestrator (EP-11) assume integralmente o papel de "cérebro" de decisão (NBA/NBO), dispensando o Global Supervisor/StateGraph de decisão da plataforma externa; o Agent Runtime (EP-12) e os channel adapters da plataforma externa (ex.: os backends de agente do Hoshikawa) tornam-se meros executores do dispatch que nosso Orchestrator produz. Em qualquer um dos dois modos, o núcleo de segurança (PEP/PDP, EP-05/EP-03) nunca muda de lugar — apenas quem calcula a NBA/NBO muda. Em ambos os modos, cada backend/agente de domínio continua registrado como um MCP Client distinto (EP-01), vinculado a um client profile/entitlement próprio por domínio+risk tier (EP-04, P6), e o "MCP Gateway" interno de qualquer plataforma consumidora (ADR-023) é sempre substituído pelo nosso Gateway/PEP como único ponto de autorização. Isso é a "elevação a Enterprise": catálogo/entitlement/auditoria governados centralmente, com a decisão de NBA/NBO podendo vir de dentro (modo B) ou de fora (modo A) do bus, por escolha modular do adotante. |
Forçar um único modo de integração (ex.: só "governança-somente"), o que subutilizaria o Service Orchestrator e obrigaria sempre uma plataforma externa de decisão; tratar o Agent Platform OCI como um substituto do E-MCP-BUS (incorreto — ele não resolve entitlement); reimplementar do zero a engenharia de orquestração já validada em ADR-022 mesmo quando o modo B é escolhido (a forma StateGraph/checkpointing/handoff continua reaproveitável dentro do nosso EP-11/EP-12). | Reformula o objetivo de EP-11 (Service Orchestrator explicitamente descrito como calculador de NBA e NBO sobre serviços/ofertas/ações do Fabric, com modelo de decisão pluggable) e de EP-13 (demonstra os dois modos — ver EP-13-T05 para o modo A e EP-13-T06, novo, para o modo B). Reforça que EP-06/EP-05 tratam qualquer "MCP Gateway" de uma plataforma consumidora como não-confiável para autorização, nos dois modos. |
| ADR-025 | Engine do PDP (fecha G3 — decisão confirmada pelo autor em 2026-09-11; renumerado de ADR-010 para ADR-025 em 2026-09-11 para não colidir com o ADR-010 do README, "Global Capability Registry Governance"). | OPA/Rego, self-hosted, OSS (Apache-2.0). Decisão definitiva, não sujeita a revisão. Verificado em 2026-09-11 via busca web: OPA está em v1.0+, com sintaxe Rego v1 como padrão (breaking change vs. v0) — EP-03 deve escrever policies em sintaxe Rego v1 desde o início. | Cedar (AWS, cita-se em §43.2); engine própria em Python. | EP-03 inteiro fica acoplado à API REST do OPA; policies versionadas como código em Rego v1. |
8.5 Estrutura proposta do repositório
Enterprise-MCP-Service-Bus/
├── README.md # arquitetura de referência (existente)
├── docs/
│ ├── RI-PLANNING.md # este documento
│ └── adr/ # ADR-001..021 em arquivos individuais (EP-16-T03)
└── RI/ # implementação executável (não existe ainda — ver G1)
├── pyproject.toml # PEP 621, uv, ruff, mypy/pyright, pytest
├── Makefile # up/down/logs/test/lint/validate-schemas
├── docker-compose.yml # stack completa (perfis: core, llm-observability)
├── README.md # quickstart da RI
├── config/ # TUDO que é configurável, em YAML
│ ├── env/ # overlays dev/ci
│ ├── clients/ # client profiles (§41) — EP-04
│ ├── capabilities/ # manifests de capability (§6.8) — EP-02
│ ├── policies/ # Rego (entitlement + transaction) — EP-03
│ ├── backends/ # config de downstream identity por adapter — EP-07
│ └── orchestrator/ # definição declarativa de jornadas/regras — EP-11
├── schemas/ # 1 JSON Schema por tipo de YAML/JSON acima
├── src/emcp_bus/
│ ├── common/ # config loader, Pydantic base models, OTel setup
│ ├── identity/ # EP-01
│ ├── registry/ # EP-02 (control plane)
│ ├── pdp/ # EP-03 (cliente OPA)
│ ├── entitlement/ # EP-04
│ ├── gateway/ # EP-05 (PEP, data plane)
│ ├── fabric/ # EP-06 (router, mediation)
│ ├── downstream/ # EP-07 (outbound identity)
│ ├── audit/ # EP-08
│ ├── profile_intelligence/ # EP-09
│ ├── offering_filter/ # EP-10
│ ├── orchestrator/ # EP-11 (grafos LangGraph)
│ └── agent_runtime/ # EP-12
├── services/
│ └── example_mcp_servers/
│ ├── sales_domain/ # EP-06
│ └── finance_domain/ # EP-06
├── agents/
│ └── example_agent/ # EP-13 (Langfuse)
├── deploy/
│ ├── keycloak/ # realm export — EP-01
│ ├── opa/ # bundles Rego — EP-03
│ ├── otel/ # collector config — EP-08
│ └── langfuse/ # compose overlay OSS — EP-13
└── tests/
├── unit/
├── contract/ # EP-15-T01
├── e2e/ # EP-15-T02
└── adversarial/ # EP-15-T03/T04
8.6 Backlog estruturado
backlog:
- epico_id: EP-00
titulo: "Bootstrap e tooling da RI"
objetivo: "Estabelecer scaffolding Python/YAML, CI e docker-compose base necessários a todos os demais épicos."
secoes_readme: ["Seção 5 do superprompt (stack)"]
tarefas:
- id: EP-00-T01
titulo: "Scaffold do projeto Python"
descricao: "Criar o diretório RI/ do zero (não existe no repositório) com pyproject.toml (PEP 621, uv), ruff, mypy/pyright estrito, pytest+pytest-asyncio, pre-commit."
componente: "tooling"
tipo: infra
prioridade: must
estimativa: P
depende_de: []
entregaveis: ["RI/pyproject.toml", "RI/.pre-commit-config.yaml", "RI/Makefile"]
criterios_aceite: ["Dado o repo clonado, quando se roda `make lint test`, então lint e um teste smoke passam sem erro."]
padroes_abertos: ["SemVer", "Conventional Commits"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: ["Escolha prematura de versões — mitigado por EP-16-T04"]
rastreabilidade: ["ADR-018"]
- id: EP-00-T02
titulo: "Framework de configuração declarativa"
descricao: "Loader YAML->Pydantic v2 com interpolação de variáveis de ambiente, overlay por ambiente (config/env/*.yaml) e exportação automática de JSON Schema por modelo."
componente: "common/config"
tipo: infra
prioridade: must
estimativa: M
depende_de: ["EP-00-T01"]
entregaveis: ["src/emcp_bus/common/config.py", "schemas/README.md (convenção de geração)"]
criterios_aceite: ["Dado um YAML inválido contra seu schema, quando carregado, então falha com erro legível apontando o campo.", "Segredos nunca são aceitos como valor literal em YAML — apenas via ${ENV_VAR}."]
padroes_abertos: ["JSON Schema"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["ADR-018"]
- id: EP-00-T03
titulo: "Pipeline de CI"
descricao: "CI (lint, type-check, testes, validação de todo YAML em config/ contra seu JSON Schema) executando a cada PR."
componente: "ci"
tipo: infra
prioridade: must
estimativa: P
depende_de: ["EP-00-T01", "EP-00-T02"]
entregaveis: [".github/workflows/ci.yml (ou equivalente)"]
criterios_aceite: ["Quando um YAML de config/ viola seu schema, então o CI falha antes de qualquer teste de integração."]
padroes_abertos: ["JSON Schema"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: []
- id: EP-00-T04
titulo: "docker-compose base (walking skeleton)"
descricao: "docker-compose.yml mínimo capaz de subir apenas o Gateway e um MCP Server de exemplo, com perfis (`core`, `llm-observability`) para adicionar serviços pesados depois."
componente: "deploy"
tipo: infra
prioridade: must
estimativa: P
depende_de: ["EP-00-T01"]
entregaveis: ["RI/docker-compose.yml"]
criterios_aceite: ["Quando `docker compose --profile core up`, então gateway e example-server sobem e respondem health check."]
padroes_abertos: ["OCI"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: []
- id: EP-00-T05
titulo: "Gestão de segredos"
descricao: "Convenção e mecanismo (.env + docker secrets, ou SOPS quando fizer sentido) para segredos: client secrets, tokens de backend, credenciais de banco — nunca em YAML versionado."
componente: "common/config"
tipo: seguranca
prioridade: must
estimativa: P
depende_de: ["EP-00-T02"]
entregaveis: ["RI/.env.example", "docs de convenção em RI/README.md#secrets"]
criterios_aceite: ["Um `git grep` por padrões de segredo nos arquivos versionados não encontra nenhum valor real.", "Todo segredo referenciado em config YAML usa ${ENV_VAR}."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: ["Vazamento acidental de secret em commit — mitigado por pre-commit hook de detecção"]
rastreabilidade: ["RNF-Segurança (§37)"]
- epico_id: EP-01
titulo: "Identidade do MCP Client e autenticação"
objetivo: "Garantir que a identidade usada para autorização venha de um mecanismo autenticado, nunca de campos autodeclarados (§5, Observação crítica sobre clientInfo)."
secoes_readme: ["§5", "§6.3", "§18", "§19", "§20", "§35.5", "§35.6"]
tarefas:
- id: EP-01-T01
titulo: "Keycloak como Identity Provider da RI"
descricao: "Serviço Keycloak no compose com realm de exemplo (script de import) representando ambiente enterprise."
componente: "identity"
tipo: infra
prioridade: must
estimativa: P
depende_de: ["EP-00-T04"]
entregaveis: ["deploy/keycloak/realm-export.json", "compose service `keycloak`"]
criterios_aceite: ["Dado o compose no ar, quando se acessa o endpoint OIDC discovery do realm, então retorna metadata válida."]
padroes_abertos: ["OAuth 2.1", "OpenID Connect"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: ["[NÃO-OSS]: nenhum — Keycloak é Apache-2.0"]
rastreabilidade: ["RF-02", "ADR-019"]
- id: EP-01-T02
titulo: "Schema de registro de MCP Client"
descricao: "YAML `config/clients/*.yaml` + Pydantic + JSON Schema mapeando client_id ↔ client Keycloak ↔ nome do client profile (ex.: Sales.Read)."
componente: "identity"
tipo: feature
prioridade: must
estimativa: P
depende_de: ["EP-00-T02"]
entregaveis: ["schemas/client-registration.schema.json", "src/emcp_bus/identity/models.py"]
criterios_aceite: ["Dado um client_id não registrado, quando consultado, então retorna 'not found' determinístico."]
padroes_abertos: ["JSON Schema"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-01"]
- id: EP-01-T03
titulo: "Middleware de autenticação do Gateway"
descricao: "Validação de access token OAuth2/OIDC (assinatura via JWKS do Keycloak, audience, expiração) antes de qualquer processamento MCP; identidade nunca lida de clientInfo autodeclarado."
componente: "identity"
tipo: seguranca
prioridade: must
estimativa: M
depende_de: ["EP-01-T01", "EP-01-T02"]
entregaveis: ["src/emcp_bus/identity/authn.py", "tests/unit/test_authn.py"]
criterios_aceite: ["Dado um token com audience incorreta, quando usado em qualquer request, então é rejeitado (Teste 8, §45).", "Dado um `clientInfo.name` autodeclarado divergente do token, quando processado, então a identidade usada é a do token, não a declarada."]
padroes_abertos: ["OAuth 2.1", "OpenID Connect"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-02", "Axiom 2", "ADR-001"]
- id: EP-01-T04
titulo: "Spike: workload identity / mTLS (extensão de produção)"
descricao: "Documentar (sem implementar) como mTLS/cloud workload identity substituiria client_credentials em produção, como ponto de extensão do middleware EP-01-T03."
componente: "identity"
tipo: spike
prioridade: could
estimativa: P
depende_de: ["EP-01-T03"]
entregaveis: ["docs/adr/ADR-019-identity-extension.md"]
criterios_aceite: ["Documento revisado explicita a interface de extensão sem exigir mudança de contrato no restante do Gateway."]
padroes_abertos: ["mTLS"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§19", "ADR-019"]
- id: EP-01-T05
titulo: "Regra de shared client"
descricao: "Validação (lint de config) que impede dois agentes distintos referenciarem o mesmo MCP Client quando seus client profiles nominais diferem (anti-pattern §18/§35)."
componente: "identity"
tipo: seguranca
prioridade: should
estimativa: P
depende_de: ["EP-01-T02"]
entregaveis: ["src/emcp_bus/identity/shared_client_lint.py"]
criterios_aceite: ["Dado dois agentes configurados apontando ao mesmo client_id com client profiles diferentes, quando o lint roda, então falha com mensagem explicando o anti-pattern."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§18", "§35.4"]
- epico_id: EP-02
titulo: "Global Capability Registry e publishing pipeline"
objetivo: "Permitir que service owners publiquem capabilities por pipeline governado, alimentando o Registry como control-plane asset (§6.6, §6.8, §23, §24)."
secoes_readme: ["§6.6", "§6.8", "§17", "§23", "§24", "§39", "§40"]
tarefas:
- id: EP-02-T01
titulo: "Schema de capability manifest"
descricao: "YAML de manifesto (baseado no exemplo §6.8) + Pydantic + JSON Schema: nome canônico `<domain>.<capability>`, versão, owner, risk_tier, data_classification, side_effects, idempotent, requiredEntitlements, backend, lifecycle."
componente: "registry"
tipo: feature
prioridade: must
estimativa: M
depende_de: ["EP-00-T02"]
entregaveis: ["schemas/capability-manifest.schema.json", "config/capabilities/README.md (convenção de naming §39)"]
criterios_aceite: ["Um manifesto sem `risk_tier` ou `owner` é rejeitado pelo schema."]
padroes_abertos: ["JSON Schema", "SemVer"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-06", "RF-11", "ADR-009"]
- id: EP-02-T02
titulo: "Publishing pipeline (gate de validação)"
descricao: "CLI/serviço que valida manifesto (schema, referência de policy existente em EP-03, health check do backend declarado) antes de permitir publicação — nenhum cadastro manual direto em produção."
componente: "registry"
tipo: feature
prioridade: must
estimativa: G
depende_de: ["EP-02-T01"]
entregaveis: ["src/emcp_bus/registry/pipeline.py", "tests/contract/test_publishing_gate.py"]
criterios_aceite: ["Dado um manifesto com política inexistente referenciada, quando submetido ao pipeline, então é rejeitado com motivo.", "Dado um manifesto válido, quando aprovado, então aparece no Registry com status `active`."]
padroes_abertos: ["JSON Schema"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-11", "ADR-011"]
- id: EP-02-T03
titulo: "Armazenamento e API de leitura do Registry"
descricao: "Persistência (SQLite dev / Postgres compartilhado) do catálogo administrativo completo + API de leitura consumida por Gateway, Fabric e Offering Filter — distinta da visão por client."
componente: "registry"
tipo: feature
prioridade: must
estimativa: M
depende_de: ["EP-00-T04", "EP-02-T01"]
entregaveis: ["src/emcp_bus/registry/store.py", "src/emcp_bus/registry/api.py"]
criterios_aceite: ["Dado o Registry populado, quando consultado por domínio, então retorna somente capabilities `active` daquele domínio."]
padroes_abertos: ["OpenAPI 3.1"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-06"]
- id: EP-02-T04
titulo: "Máquina de estados de lifecycle"
descricao: "Implementar nominate→review→register→publish→observe→(change|deprecate→retire) do §23 como status controlado, com transições auditadas (EP-08)."
componente: "registry"
tipo: feature
prioridade: should
estimativa: M
depende_de: ["EP-02-T03"]
entregaveis: ["src/emcp_bus/registry/lifecycle.py"]
criterios_aceite: ["Uma capability não pode ir de `register` direto para `publish` sem passar por `review` aprovado."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§23"]
- id: EP-02-T05
titulo: "Manifests de exemplo (Sales, Finance)"
descricao: "Seed data cobrindo tiers R1–R3: sales.customer.get/search (R1), sales.quote.create/update (R2), finance.payment.execute (R3, exige aprovação)."
componente: "registry"
tipo: feature
prioridade: must
estimativa: P
depende_de: ["EP-02-T02", "EP-02-T03"]
entregaveis: ["config/capabilities/sales/*.yaml", "config/capabilities/finance/*.yaml"]
criterios_aceite: ["Todos os manifestos de seed passam pelo pipeline (EP-02-T02) sem intervenção manual."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§7.1", "§17"]
- epico_id: EP-03
titulo: "PDP / Policy Engine (OPA)"
objetivo: "Avaliar entitlement policy e transaction policy de forma determinística e auditável, sem nunca conceder tool ausente do client entitlement (§6.5, §25)."
secoes_readme: ["§6.5", "§16", "§25", "§42", "§48(3)"]
tarefas:
- id: EP-03-T01
titulo: "Integração com OPA"
descricao: "Serviço OPA no compose + client Python (REST) para avaliação de policies; contrato de decisão ALLOW/DENY/REQUIRE_APPROVAL."
componente: "pdp"
tipo: infra
prioridade: must
estimativa: M
depende_de: ["EP-00-T04"]
entregaveis: ["deploy/opa/", "src/emcp_bus/pdp/client.py"]
criterios_aceite: ["Dado o OPA no ar, quando uma policy de teste é avaliada via client Python, então o resultado corresponde ao esperado pela suíte `opa test`."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: ["[NÃO-OSS]: nenhum — OPA é Apache-2.0"]
rastreabilidade: ["ADR-025"]
- id: EP-03-T02
titulo: "Entitlement policy em Rego"
descricao: "Policy `permit if requested_tool in entitlement(client)` (§25 Passo 1), com dados de entitlement carregados do client profile (EP-04-T01)."
componente: "pdp"
tipo: seguranca
prioridade: must
estimativa: M
depende_de: ["EP-03-T01", "EP-04-T01"]
entregaveis: ["config/policies/entitlement.rego", "tests/unit/policies/entitlement_test.rego"]
criterios_aceite: ["Dado client=Sales.Read e tool=finance.createPayment, quando avaliado, então DENY (Teste 1/2, §45)."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-05", "Axiom 4", "ADR-025"]
- id: EP-03-T03
titulo: "Transaction policy em Rego"
descricao: "Policy de argumentos/recursos (§25 Passo 2, exemplo quote.create: amount ≤ limit, region, classification)."
componente: "pdp"
tipo: seguranca
prioridade: must
estimativa: M
depende_de: ["EP-03-T01"]
entregaveis: ["config/policies/transaction.rego", "tests/unit/policies/transaction_test.rego"]
criterios_aceite: ["Dado quote.create com amount acima do limite do client, quando avaliado, então DENY mesmo que a tool esteja no entitlement."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-05", "§6.5.B"]
- id: EP-03-T04
titulo: "Contrato de decisão do PDP"
descricao: "Modelo Pydantic unificado de decisão (ALLOW/DENY/REQUIRE_APPROVAL + policy_version + reason_code) consumido pelo Gateway."
componente: "pdp"
tipo: feature
prioridade: must
estimativa: P
depende_de: ["EP-03-T02", "EP-03-T03"]
entregaveis: ["src/emcp_bus/pdp/models.py"]
criterios_aceite: ["Toda resposta do PDP inclui policy_version e reason_code, nunca apenas um booleano."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§27 (campos de auditoria)"]
- epico_id: EP-04
titulo: "Entitlement Manager"
objetivo: "Resolver e versionar o MaximumEntitlement por client, com provenance e invalidação em revogação (§6.9, §41)."
secoes_readme: ["§6.9", "§7.1", "§7.2", "§17", "§31", "§41"]
tarefas:
- id: EP-04-T01
titulo: "Schema de client profile"
descricao: "YAML `config/clients profile` (§41 exemplo) + Pydantic + JSON Schema: name, owner, risk_ceiling, allowed_tools, restrictions, credential_policy, audit level."
componente: "entitlement"
tipo: feature
prioridade: must
estimativa: P
depende_de: ["EP-00-T02"]
entregaveis: ["schemas/client-profile.schema.json", "config/clients/profiles/*.yaml (Sales.Read/Write/Approve, Finance.*)"]
criterios_aceite: ["Um client profile sem `risk_ceiling` é rejeitado pelo schema."]
padroes_abertos: ["JSON Schema"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-01", "ADR-008", "P6"]
- id: EP-04-T02
titulo: "Serviço resolveMaximumEntitlement"
descricao: "Combina client profile + decisão de entitlement do PDP, produz versão cacheável (`entitlementVersion`) com provenance (qual regra incluiu/restringiu cada capability)."
componente: "entitlement"
tipo: feature
prioridade: must
estimativa: M
depende_de: ["EP-04-T01", "EP-03-T02", "EP-03-T04"]
entregaveis: ["src/emcp_bus/entitlement/manager.py"]
criterios_aceite: ["Dado client=Sales.Write, quando resolvido, então o conjunto retornado é exatamente {customer.search, customer.get, order.get, inventory.check, quote.create, quote.update} (§7.1)."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-01", "RF-12", "Axiom 2"]
- id: EP-04-T03
titulo: "Revogação e invalidação de cache"
descricao: "Endpoint/evento de revogação de client ou policy que invalida entitlement cacheado e emite `policy_changed`/`client_revoked` (consumido por EP-08 e EP-05)."
componente: "entitlement"
tipo: seguranca
prioridade: must
estimativa: M
depende_de: ["EP-04-T02"]
entregaveis: ["src/emcp_bus/entitlement/revocation.py"]
criterios_aceite: ["Dado um client revogado, quando uma nova tools/call chega, então é negada mesmo com cache de tools/list ainda não expirado (Teste 6, §45)."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-08"]
- id: EP-04-T04
titulo: "Contrato de versão de entitlement"
descricao: "Expor `entitlementVersion` de forma consistente para PDP, Gateway e Orchestrator, usado na correlação ponta a ponta (RF-17)."
componente: "entitlement"
tipo: feature
prioridade: should
estimativa: P
depende_de: ["EP-04-T02"]
entregaveis: ["src/emcp_bus/entitlement/models.py (EntitlementVersion)"]
criterios_aceite: ["Toda resposta de resolveMaximumEntitlement inclui entitlementVersion não-nulo."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-17"]
- epico_id: EP-05
titulo: "MCP Gateway / PEP"
objetivo: "Implementar o PEP: discovery filtrado, execution enforcement, caching privado, anti-bypass e fail-closed (§6.4, §9, §10, §12, §13, §21, §22, §38)."
secoes_readme: ["§6.4", "§9", "§10", "§11", "§12", "§13", "§21", "§22", "§35.3", "§38", "§42"]
tarefas:
- id: EP-05-T01
titulo: "Gateway skeleton (walking skeleton)"
descricao: "Servidor MCP (SDK oficial `mcp`, transporte Streamable HTTP) que recebe tools/list e tools/call e os encaminha, sem enforcement ainda, a um roteamento estático de um único MCP Server de exemplo."
componente: "gateway"
tipo: feature
prioridade: must
estimativa: M
depende_de: ["EP-00-T04", "EP-06-T02"]
entregaveis: ["src/emcp_bus/gateway/server.py"]
criterios_aceite: ["Dado o compose no ar (perfil core), quando um cliente MCP chama tools/list e tools/call, então recebe resposta do example server via Gateway, com trace_id visível no log (EP-08-T01)."]
padroes_abertos: ["MCP", "JSON-RPC 2.0"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: "Roteamento/proxy simples — pertence ao núcleo do bus em Python puro, não a um grafo de agente."
riscos: []
rastreabilidade: ["Marco M0"]
- id: EP-05-T02
titulo: "Filtered tools/list"
descricao: "Implementar sequência do §9: autenticar client (EP-01-T03), resolver MaximumEntitlement (EP-04-T02), obter catálogo do Registry (EP-02-T03), retornar interseção."
componente: "gateway"
tipo: seguranca
prioridade: must
estimativa: M
depende_de: ["EP-05-T01", "EP-01-T03", "EP-04-T02"]
entregaveis: ["src/emcp_bus/gateway/discovery.py"]
criterios_aceite: ["Dado client=Sales.Read, quando tools/list é chamado, então finance.createPayment NUNCA aparece na resposta (Teste 1, §45)."]
padroes_abertos: ["MCP"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-03", "P4", "P5 (invariantes §9)"]
- id: EP-05-T03
titulo: "Cache privado de tools/list"
descricao: "Cache com chave = client_identity+authorization_context+policy_version+params, cacheScope=private, TTL curto configurável, invalidado em revogação (EP-04-T03). Verificado em 2026-09-11: SEP-2549 (ttlMs/cacheScope, GA na revisão 2026-07-28) já é suportado nativamente pelo SDK Python `mcp` v2 via `cache_hints=` no servidor — usar o primitivo do SDK em vez de reimplementar cache HTTP manual."
componente: "gateway"
tipo: seguranca
prioridade: must
estimativa: M
depende_de: ["EP-05-T02"]
entregaveis: ["src/emcp_bus/gateway/cache.py"]
criterios_aceite: ["Dado catálogo cacheado de Sales.Read, quando Finance.Payments faz a mesma requisição, então nunca recebe o cache de Sales.Read (Teste 5, §45)."]
padroes_abertos: ["MCP 2026-07-28 (SEP-2549) — confirmado via busca web em 2026-09-11"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§13", "Threat: Cache leakage (§28)"]
- id: EP-05-T04
titulo: "Enforcement de tools/call"
descricao: "Reautorização independente do discovery (§10): client capability check → transaction policy → REQUIRE_APPROVAL/ALLOW/DENY determinístico."
componente: "gateway"
tipo: seguranca
prioridade: must
estimativa: G
depende_de: ["EP-05-T01", "EP-03-T04"]
entregaveis: ["src/emcp_bus/gateway/enforcement.py"]
criterios_aceite: ["Dado client=Sales.Read chamando diretamente tools/call(finance.createPayment) sem nunca ter visto a tool em tools/list, quando executado, então DENY determinístico (Teste 2, §45)."]
padroes_abertos: ["MCP"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-04", "Axiom 5", "ADR-003"]
- id: EP-05-T05
titulo: "Rate limiting e quota por client"
descricao: "Limites configuráveis por client profile, aplicados no Gateway antes do roteamento ao Fabric."
componente: "gateway"
tipo: seguranca
prioridade: should
estimativa: M
depende_de: ["EP-05-T01"]
entregaveis: ["src/emcp_bus/gateway/rate_limit.py"]
criterios_aceite: ["Dado um client excedendo seu limite configurado, quando chama tools/call, então recebe 429/DENY determinístico sem consultar backend."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RNF-Disponibilidade (§37)", "Threat: bulk exfiltration (§28)"]
- id: EP-05-T06
titulo: "Fail-closed em indisponibilidade"
descricao: "Implementar tabela do §38: PDP indisponível → fail closed; Registry indisponível → cache válido dentro do contexto; approval service indisponível → nega high-risk."
componente: "gateway"
tipo: seguranca
prioridade: must
estimativa: M
depende_de: ["EP-05-T04"]
entregaveis: ["src/emcp_bus/gateway/failure_policy.py"]
criterios_aceite: ["Dado o OPA fora do ar, quando qualquer tools/call chega, então a resposta é DENY, nunca ALLOW por omissão."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RNF-Disponibilidade (§37)", "§38"]
- id: EP-05-T07
titulo: "Prevenção de bypass do PEP"
descricao: "Isolamento de rede no compose (Fabric/backends só aceitam origem do Gateway) + detecção de chamadas que não atravessaram o PEP em telemetria."
componente: "gateway"
tipo: seguranca
prioridade: must
estimativa: M
depende_de: ["EP-06-T01"]
entregaveis: ["docker-compose network segmentation", "src/emcp_bus/audit/bypass_detection.py"]
criterios_aceite: ["Dado acesso direto tentado ao Fabric/backend a partir de fora da rede do Gateway, quando tentado, então falha (Teste 7, §45)."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§21", "ADR-007", "Axiom 7"]
- id: EP-05-T08
titulo: "Consistência header/body (revisão MCP 2026-07-28)"
descricao: "Validar consistência entre `Mcp-Method`/`Mcp-Name` e o corpo JSON-RPC antes de decisão de autorização, produzindo representação canônica única (§22). Verificado em 2026-09-11: a revisão 2026-07-28 é real (GA, SEP-2243) e o SDK Python `mcp` v2 já expõe esses headers nativamente no transporte Streamable HTTP — a tarefa é validar/canonicalizar, não implementar o parsing do zero."
componente: "gateway"
tipo: seguranca
prioridade: should
estimativa: M
depende_de: ["EP-05-T04"]
entregaveis: ["src/emcp_bus/gateway/canonical_request.py"]
criterios_aceite: ["Dado header Mcp-Name divergente do `params.name` do body, quando recebido, então a requisição é rejeitada por ambiguidade, nunca processada com um dos dois valores 'na confiança'."]
padroes_abertos: ["MCP 2026-07-28 (SEP-2243) — confirmado via busca web em 2026-09-11"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§22"]
- epico_id: EP-06
titulo: "Enterprise MCP Fabric e servidores de domínio de exemplo"
objetivo: "Resolver capability em implementação concreta via roteamento por domínio, com servidores MCP de exemplo demonstrando risk tiers (§6.7, §32, §34)."
secoes_readme: ["§6.7", "§17", "§32", "§34"]
tarefas:
- id: EP-06-T01
titulo: "Fabric core (capability router)"
descricao: "Router que resolve `<domain>.<capability>` para o adapter/backend correto usando o Registry (EP-02-T03), com retries/timeouts e telemetria."
componente: "fabric"
tipo: feature
prioridade: must
estimativa: M
depende_de: ["EP-02-T03", "EP-06-T02", "EP-06-T03"]
entregaveis: ["src/emcp_bus/fabric/router.py"]
criterios_aceite: ["Dado uma capability publicada em Sales, quando roteada, então chega ao MCP Server de Sales, não ao de Finance."]
padroes_abertos: ["MCP"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: "Roteamento determinístico simples — não é fluxo de agente."
riscos: []
rastreabilidade: ["RF component (§6.7)"]
- id: EP-06-T02
titulo: "MCP Server de exemplo: domínio Sales"
descricao: "Servidor MCP (SDK oficial, padrão FastMCP — `from mcp.server.fastmcp import FastMCP`, `@mcp.tool()`, transporte `streamable-http` — confirmado como padrão de referência em ADR-022) com customer.search/get (R1), order.get (R1), inventory.check (R1), quote.create/update (R2) contra dados mockados. O servidor permanece 'burro': nunca decide autorização (ADR-023) — toda decisão vem do Gateway/PDP antes da chamada chegar aqui."
componente: "fabric"
tipo: feature
prioridade: must
estimativa: M
depende_de: ["EP-00-T01"]
entregaveis: ["services/example_mcp_servers/sales_domain/"]
criterios_aceite: ["Cada tool expõe input/output schema conforme manifesto correspondente (EP-02-T05)."]
padroes_abertos: ["MCP"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§7.1", "Marco M0"]
- id: EP-06-T03
titulo: "MCP Server de exemplo: domínio Finance"
descricao: "Servidor MCP com invoice.get (R1), payment.create (R2), payment.execute (R3, sujeito a aprovação — EP-14) contra dados mockados."
componente: "fabric"
tipo: feature
prioridade: must
estimativa: M
depende_de: ["EP-00-T01"]
entregaveis: ["services/example_mcp_servers/finance_domain/"]
criterios_aceite: ["payment.execute >= 10k dispara REQUIRE_APPROVAL na policy correspondente (§26)."]
padroes_abertos: ["MCP"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§17", "§26"]
- id: EP-06-T04
titulo: "Adapter REST genérico"
descricao: "Padrão de adapter reutilizável que traduz uma capability MCP para uma chamada REST (documentada via OpenAPI) de backend, usado pelos dois domínios."
componente: "fabric"
tipo: feature
prioridade: should
estimativa: M
depende_de: ["EP-06-T01"]
entregaveis: ["src/emcp_bus/fabric/adapters/rest_adapter.py"]
criterios_aceite: ["Dado um novo backend descrito por OpenAPI, quando configurado via YAML, então nenhum código Python novo é necessário para expô-lo (apenas config)."]
padroes_abertos: ["OpenAPI 3.1"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§6.7"]
- epico_id: EP-07
titulo: "Downstream identity (inbound vs outbound)"
objetivo: "Evitar blind token passthrough — identidade de entrada no Fabric não é reutilizada automaticamente no backend (§20)."
secoes_readme: ["§20", "§35.6"]
tarefas:
- id: EP-07-T01
titulo: "Componente de outbound identity"
descricao: "Antes de chamar o backend, o Fabric obtém credencial apropriada (token exchange RFC 8693 via Keycloak, ou service account por adapter) em vez de repassar o token do client."
componente: "downstream"
tipo: seguranca
prioridade: must
estimativa: M
depende_de: ["EP-06-T04", "EP-01-T01"]
entregaveis: ["src/emcp_bus/downstream/identity.py"]
criterios_aceite: ["Dado um tools/call autorizado, quando a chamada chega ao backend mock, então o token usado no backend é diferente do token do MCP Client (audience distinta)."]
padroes_abertos: ["OAuth 2.1 (token exchange RFC 8693)"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-09", "ADR-006", "ADR-020"]
- id: EP-07-T02
titulo: "Config de credenciais por backend"
descricao: "Schema YAML `config/backends/*.yaml` (sem segredos inline — via ${ENV_VAR}, ver EP-00-T05) declarando o modelo de outbound identity por backend."
componente: "downstream"
tipo: feature
prioridade: must
estimativa: P
depende_de: ["EP-07-T01", "EP-00-T05"]
entregaveis: ["schemas/backend-credentials.schema.json", "config/backends/*.yaml"]
criterios_aceite: ["Um arquivo com segredo literal (não ${ENV_VAR}) é rejeitado pelo schema/CI."]
padroes_abertos: ["JSON Schema"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-09", "ADR-020"]
- epico_id: EP-08
titulo: "Auditoria, correlação e observabilidade"
objetivo: "Tornar toda decisão relevante rastreável ponta a ponta, sem registrar segredos (§27, RF-17)."
secoes_readme: ["§27", "§28 (traceability gap)", "RF-17"]
tarefas:
- id: EP-08-T01
titulo: "Tracing OTel de base"
descricao: "Instrumentação OpenTelemetry (traces/metrics/logs) com propagação W3C Trace Context em Gateway, Fabric e serviços de exemplo."
componente: "audit"
tipo: observabilidade
prioridade: must
estimativa: M
depende_de: ["EP-00-T01"]
entregaveis: ["src/emcp_bus/common/otel.py", "deploy/otel/collector-config.yaml"]
criterios_aceite: ["Dado um tools/call ponta a ponta, quando inspecionado no coletor, então um único trace_id conecta Gateway→Fabric→backend mock (Marco M0)."]
padroes_abertos: ["OpenTelemetry", "W3C Trace Context"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RNF-Performance (§37)", "Marco M0"]
- id: EP-08-T02
titulo: "Emissor de eventos de auditoria"
descricao: "Taxonomia de eventos do §27 (client_authenticated, tools_list_filtered, tool_call_denied, etc.) com sink append-only (arquivo em dev, Postgres opcional), excluindo tokens/secrets/dados sensíveis. Adota o vocabulário IC/NOC/GRL (evento de negócio-jornada / operacional-erro / guardrail-governança) do Agent Platform OCI (ADR-022) como categorização do `EventEnvelope`, carregando `decisionId`/`policyDecisionId`/`entitlementVersion` (ADR-017) como campos extras do payload em vez de reinventar taxonomia."
componente: "audit"
tipo: observabilidade
prioridade: must
estimativa: M
depende_de: ["EP-08-T01"]
entregaveis: ["src/emcp_bus/audit/events.py", "src/emcp_bus/audit/sink.py"]
criterios_aceite: ["Um evento de auditoria nunca contém o valor de um token/secret, apenas seu hash/referência (§27, 'Não registrar').", "Toda decisão DENY do PDP gera um evento `tool_call_denied` com reason_code.", "Todo evento é classificável em IC (negócio/jornada), NOC (operacional/erro) ou GRL (guardrail/governança)."]
padroes_abertos: ["CloudEvents"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-07", "ADR-022"]
- id: EP-08-T03
titulo: "Correlação ponta a ponta"
descricao: "Encadear profileVersion + entitlementVersion + decisionId + policyDecisionId + mcpRequestId através de todos os componentes, expostos no trace e no evento de auditoria."
componente: "audit"
tipo: observabilidade
prioridade: must
estimativa: M
depende_de: ["EP-08-T02", "EP-04-T04"]
entregaveis: ["src/emcp_bus/audit/correlation.py"]
criterios_aceite: ["Dada uma jornada completa (perfil→entitlement→NBA→execução), quando consultada por decisionId, então todos os eventos relacionados são recuperáveis (fecha Threat: Traceability gap, §28)."]
padroes_abertos: ["W3C Trace Context"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-17", "ADR-017"]
- id: EP-08-T04
titulo: "Stack local de observabilidade"
descricao: "OTel Collector + backend de traces OSS (Jaeger ou Tempo) no compose (`llm-observability` profile opcional para não pesar o M0)."
componente: "audit"
tipo: infra
prioridade: should
estimativa: P
depende_de: ["EP-08-T01"]
entregaveis: ["compose services `otel-collector`, `jaeger`"]
criterios_aceite: ["Traces do EP-08-T01 são visíveis na UI do Jaeger local."]
padroes_abertos: ["OpenTelemetry"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: []
- epico_id: EP-09
titulo: "Profile Intelligence (contrato genérico)"
objetivo: "Expor uma visão de perfil versionada e substituível que apenas ordena/reduz candidatas, nunca concede entitlement (§6.10)."
secoes_readme: ["§6.10", "Axiom 11"]
tarefas:
- id: EP-09-T01
titulo: "Contrato ProfileView"
descricao: "Schema JSON + Pydantic do exemplo §6.10 (subjectRef, profileVersion, segments, attributes, reasonCodes, expiresAt)."
componente: "profile_intelligence"
tipo: feature
prioridade: must
estimativa: P
depende_de: ["EP-00-T02"]
entregaveis: ["schemas/profile-view.schema.json", "src/emcp_bus/profile_intelligence/models.py"]
criterios_aceite: ["Um ProfileView sem `expiresAt` ou `profileVersion` é rejeitado pelo schema."]
padroes_abertos: ["JSON Schema"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-13", "ADR-013"]
- id: EP-09-T02
titulo: "Stub de referência (regras determinísticas)"
descricao: "Implementação plugável (`ProfileIntelligenceProvider`) baseada em regras sobre atributos sintéticos (ver P7) — não implementa clusterização real nem taxonomia proprietária."
componente: "profile_intelligence"
tipo: feature
prioridade: must
estimativa: M
depende_de: ["EP-09-T01"]
entregaveis: ["src/emcp_bus/profile_intelligence/rule_based_provider.py"]
criterios_aceite: ["Dado o mesmo subjectRef e mesma janela temporal, quando resolvido duas vezes, então produz o mesmo profileVersion (determinismo para fins de demo)."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: "Regra determinística, sem estado conversacional — não requer LangGraph."
riscos: ["Risco de escopo: tentar implementar clusterização 'de verdade' sem dataset real — mitigado por P7."]
rastreabilidade: ["RF-13", "P7"]
- id: EP-09-T03
titulo: "Guardrails de governança de dados"
descricao: "subjectRef pseudonimizado, log de lineage de features/versão/janela temporal, stub de monitor de drift (§6.10, requisitos de governança)."
componente: "profile_intelligence"
tipo: seguranca
prioridade: should
estimativa: M
depende_de: ["EP-09-T02"]
entregaveis: ["src/emcp_bus/profile_intelligence/governance.py"]
criterios_aceite: ["Nenhum ProfileView expõe PII direta — apenas subjectRef pseudonimizado (Threat: Sensitive attribute leakage, §28)."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§6.10", "Threat §28"]
- epico_id: EP-10
titulo: "Offering Filter"
objetivo: "Intersectar catálogo ativo, entitlement, perfil e contexto/consentimento sem nunca reintroduzir capability fora do entitlement (§6.11, §7.3)."
secoes_readme: ["§6.11", "§7.3", "Axiom 8/11"]
tarefas:
- id: EP-10-T01
titulo: "Serviço Offering Filter"
descricao: "FilteredOfferings = ActiveCatalog ∩ MaximumEntitlement ∩ SubjectAndContextConstraints, consumindo Registry (EP-02), Entitlement Manager (EP-04), ProfileView (EP-09) e consentimento/contexto declarados."
componente: "offering_filter"
tipo: feature
prioridade: must
estimativa: M
depende_de: ["EP-02-T03", "EP-04-T02", "EP-09-T01"]
entregaveis: ["src/emcp_bus/offering_filter/service.py"]
criterios_aceite: ["Dado um item fora do MaximumEntitlement do client, quando o filtro roda com qualquer score de perfil, então o item nunca aparece em FilteredOfferings."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: "Interseção de conjuntos determinística — não é fluxo de agente."
riscos: []
rastreabilidade: ["RF-14", "ADR-014"]
- id: EP-10-T02
titulo: "Testes de propriedade da invariante de subconjunto"
descricao: "Testes baseados em propriedade (ex.: Hypothesis) provando FilteredOfferings ⊆ MaximumEntitlement ⊆ GlobalCatalog para entradas geradas aleatoriamente (§7.3)."
componente: "offering_filter"
tipo: teste
prioridade: must
estimativa: M
depende_de: ["EP-10-T01"]
entregaveis: ["tests/unit/test_offering_filter_invariants.py"]
criterios_aceite: ["1000+ casos gerados aleatoriamente não produzem nenhuma violação da invariante de subconjunto."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§7.3", "RF-14"]
- epico_id: EP-11
titulo: "Service Orchestrator (NBA/NBO)"
objetivo: "Calcular a Next Best Action e a Next Best Offer (NBA/NBO) como decisão de orquestração — nunca de autorização — sobre estado de jornada, regras, guardrails, memória e o universo de serviços/ofertas/ações que o MCP Fabric expõe (§6.12, §10.1). Por ADR-024, este componente é deliberadamente robusto o suficiente (modelo de decisão pluggable, incluindo IA de ranking/recomendação) para operar tanto como fonte de FilteredOfferings para uma plataforma de orquestração externa (modo A) quanto como o único cérebro de decisão de uma integração ponta a ponta, substituindo a camada de decisão dessa plataforma externa (modo B) — modularidade + compatibilidade = escolha do adotante, nunca uma imposição da RI."
secoes_readme: ["§6.12", "§10.1", "P9 (NBA não é autorização)", "Axiom 12", "ADR-024"]
tarefas:
- id: EP-11-T01
titulo: "Contrato DecisionContext"
descricao: "Modelo Pydantic versionado combinando ProfileView, identidade autenticada, EntitlementLimits, FilteredOfferings e RuntimeContext (§1, contrato formal)."
componente: "orchestrator"
tipo: feature
prioridade: must
estimativa: P
depende_de: ["EP-09-T01", "EP-04-T04", "EP-10-T01"]
entregaveis: ["src/emcp_bus/orchestrator/decision_context.py"]
criterios_aceite: ["DecisionContext é serializável/versionado e rejeita construção sem entitlementVersion."]
padroes_abertos: ["JSON Schema"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§1"]
- id: EP-11-T02
titulo: "Grafo de jornada em LangGraph"
descricao: "StateGraph com estado de jornada, objetivo e memória; checkpointer SQLite (dev, com ponto de extensão para mongodb/produção — padrão de providers plugáveis do ADR-022). Nós fixos: resolve profile → resolve entitlement → filter offerings → decide NBA, seguindo o padrão de grafo corporativo fixo (dev só acrescenta nós de domínio, nunca mexe na espinha dorsal) descrito em `docs/research/hoshikawa-agent-platform-oci.md`."
componente: "orchestrator"
tipo: feature
prioridade: must
estimativa: G
depende_de: ["EP-11-T01"]
entregaveis: ["src/emcp_bus/orchestrator/graph.py", "src/emcp_bus/orchestrator/state.py (TypedDict do estado de jornada)"]
criterios_aceite: ["Dado um checkpoint salvo, quando o processo reinicia, então a jornada retoma do último estado sem reprocessar do zero."]
padroes_abertos: []
usa_langgraph: true
usa_langfuse: false
justificativa_langgraph_langfuse: "Fluxo com estado, múltiplos passos e necessidade de checkpointing/retomada — critério explícito da seção 6 do superprompt para uso de LangGraph."
riscos: ["Overhead de checkpointing para um fluxo ainda simples na RI — mitigado mantendo o grafo mínimo até M3"]
rastreabilidade: ["RF-15", "ADR-015", "ADR-022"]
- id: EP-11-T03
titulo: "Nó de decisioning (NBA/NBO)"
descricao: "Função de decisão versionada combinando rules + guardrails sobre DecisionContext, produzindo NBA/NBO (§6.13, exemplo JSON) e persistindo a trilha de decisão. Modelo de decisão pluggable (interface `NBADecisionModel`): implementação de referência determinística (regras/prioridade) na RI, com ponto de extensão explícito para um modelo de IA de ranking/recomendação sobre `FilteredOfferings` (serviços/ofertas/ações do Fabric) — sempre restrito ao universo já filtrado por entitlement (Axiom 8/11), nunca decidindo autorização."
componente: "orchestrator"
tipo: feature
prioridade: must
estimativa: M
depende_de: ["EP-11-T02"]
entregaveis: ["src/emcp_bus/orchestrator/decisioning.py", "src/emcp_bus/orchestrator/nba_model.py (interface pluggable)"]
criterios_aceite: ["Toda NBA/NBO emitida inclui reasonCodes, policyContext e expiresAt (§6.13).", "Trocar a implementação de `NBADecisionModel` (regras → modelo de IA) não exige mudança de contrato em EP-05/EP-03/EP-04 — a superfície de autorização é idêntica independentemente do modelo de decisão."]
padroes_abertos: []
usa_langgraph: true
usa_langfuse: false
justificativa_langgraph_langfuse: "Nó do grafo de jornada definido em EP-11-T02."
riscos: []
rastreabilidade: ["RF-15", "ADR-024"]
- id: EP-11-T04
titulo: "Recálculo e degradação segura"
descricao: "Branch DENY/indisponível do fluxo §10.1: recalcular NBA, degradar com segurança ou encerrar jornada quando o Gateway nega a execução — a NBA negada NUNCA é substituída por bypass."
componente: "orchestrator"
tipo: seguranca
prioridade: must
estimativa: M
depende_de: ["EP-11-T03", "EP-05-T04"]
entregaveis: ["src/emcp_bus/orchestrator/fallback.py"]
criterios_aceite: ["Dado um DENY do Gateway, quando recebido pelo Orchestrator, então nenhuma tentativa de execução direta ao Fabric ocorre — apenas recálculo ou encerramento auditado."]
padroes_abertos: []
usa_langgraph: true
usa_langfuse: false
justificativa_langgraph_langfuse: "Edge condicional do StateGraph de EP-11-T02."
riscos: []
rastreabilidade: ["§10.1", "Axiom 12", "P9"]
- id: EP-11-T05
titulo: "Interrupção human-in-the-loop"
descricao: "Nó de interrupção do grafo (`interrupt`) disparado quando a NBA envolve capability REQUIRE_APPROVAL, integrando com EP-14."
componente: "orchestrator"
tipo: feature
prioridade: should
estimativa: M
depende_de: ["EP-11-T03"]
entregaveis: ["src/emcp_bus/orchestrator/hitl_node.py"]
criterios_aceite: ["Dada uma NBA de alto risco, quando o grafo chega ao nó de aprovação, então a execução pausa até resposta de EP-14, sem timeout silencioso que vire ALLOW."]
padroes_abertos: []
usa_langgraph: true
usa_langfuse: false
justificativa_langgraph_langfuse: "Human-in-the-loop com pausa/retomada de estado — critério explícito da seção 6 do superprompt."
riscos: []
rastreabilidade: ["RF-10", "§26"]
- epico_id: EP-12
titulo: "Agent Runtime e channel adapters"
objetivo: "Traduzir a NBA em execução coordenada — sessão, contexto mínimo, handoff — sem nunca substituir o token do MCP Client (§6.13)."
secoes_readme: ["§6.13", "ADR-016"]
tarefas:
- id: EP-12-T01
titulo: "Serviço de dispatch"
descricao: "Consome o objeto de dispatch da NBA (§6.13, exemplo JSON), gerencia sessão/correlação e invoca o MCP Client correspondente ao agente/canal selecionado."
componente: "agent_runtime"
tipo: feature
prioridade: must
estimativa: M
depende_de: ["EP-11-T03", "EP-01-T02"]
entregaveis: ["src/emcp_bus/agent_runtime/dispatcher.py"]
criterios_aceite: ["O Agent Runtime nunca gera ou armazena credenciais próprias — usa exclusivamente o MCP Client já registrado (EP-01)."]
padroes_abertos: ["MCP"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: "Dispatch é invocação direta de um MCP Client já autenticado — não há estado cíclico próprio aqui (o estado vive no Orchestrator)."
riscos: []
rastreabilidade: ["RF-16", "ADR-016"]
- id: EP-12-T02
titulo: "Channel adapter conversacional"
descricao: "Um adapter concreto (app/conversacional) para a demo da RI, encapsulando diferenças de canal citadas em §6.13/§3.3."
componente: "agent_runtime"
tipo: feature
prioridade: must
estimativa: M
depende_de: ["EP-12-T01"]
entregaveis: ["src/emcp_bus/agent_runtime/channels/conversational.py"]
criterios_aceite: ["Uma NBA com channel=app é entregue ao adapter correto; um channel não suportado falha de forma explícita, não silenciosa."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-16"]
- id: EP-12-T03
titulo: "Handoff entre agentes"
descricao: "Lógica de handoff (ex.: engagement-assistant → especialista de domínio) coordenada como subgrafo LangGraph, preservando contexto mínimo necessário e correlação. Inspirado no padrão de dois níveis do Agent Platform OCI (ADR-022): handoff intra-processo via aresta condicional de retorno, e um campo de metadata análogo a `metadata.handover_backend` quando o handoff cruza para outro agente/serviço. Restrição de segurança explícita (ADR-023): o campo de handoff é só um sinal de intenção de roteamento — a tools/list e tools/call do agente-destino continuam sendo autorizadas do zero pelo Gateway/PDP (EP-05), nunca herdadas do agente de origem."
componente: "agent_runtime"
tipo: feature
prioridade: could
estimativa: M
depende_de: ["EP-12-T01"]
entregaveis: ["src/emcp_bus/agent_runtime/handoff_graph.py"]
criterios_aceite: ["Dado um handoff, quando concluído, então o novo agente recebe apenas o contexto mínimo necessário, não o histórico bruto completo.", "O MCP Client/entitlement do agente-destino após handoff é resolvido independentemente, nunca herdado do agente de origem."]
padroes_abertos: []
usa_langgraph: true
usa_langfuse: false
justificativa_langgraph_langfuse: "Coordenação multiagente com handoff — critério explícito da seção 6 do superprompt (padrão supervisor/handoff)."
riscos: ["Feature poderia ser considerada fora do essencial da RI — marcada `could`, não bloqueia marcos principais."]
rastreabilidade: ["§6.13", "ADR-022", "ADR-023"]
- id: EP-12-T04
titulo: "Identity Resolver / BusinessContext"
descricao: "Adota o padrão `identity.yaml`/BusinessContext do Agent Platform OCI (ADR-022): aliases declarativos de nomes de canal → chaves canônicas de negócio (ex.: customer_key, contract_key, session_key), usados para montar argumentos de tool de forma padronizada. É puramente normalização de identidade de negócio — nunca decide autorização (a diferença explícita do achado do ADR-023: aqui o campo resolvido não deve, em nenhuma hipótese, alimentar a decisão do PDP)."
componente: "agent_runtime"
tipo: feature
prioridade: should
estimativa: M
depende_de: ["EP-12-T01"]
entregaveis: ["schemas/identity-resolver.schema.json", "config/orchestrator/identity.yaml", "src/emcp_bus/agent_runtime/identity_resolver.py"]
criterios_aceite: ["Dado um payload de canal com um alias declarado (ex.: `msisdn`), quando resolvido, então produz a chave canônica correta (`customer_key`).", "O resultado do Identity Resolver nunca é aceito como entrada do PDP (EP-03) — apenas do Fabric/MCP Parameter Mapping."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["ADR-022", "ADR-023"]
- epico_id: EP-13
titulo: "Agente de exemplo e instrumentação Langfuse"
objetivo: "Demonstrar um agente LLM real consumindo o catálogo filtrado, com observabilidade de LLM via Langfuse self-hosted e mascaramento de PII (§6.1). Por ADR-024, o objetivo final do épico vai além de um único agente: demonstrar a posição de 'elevação a Enterprise' do E-MCP-BUS — múltiplos backends/agentes (padrão Agent Platform OCI/Hoshikawa), cada um com seu próprio MCP Client e entitlement, orquestrados por um Global Supervisor acima do bus, sem que a troca de backend amplie privilégio (EP-13-T05)."
secoes_readme: ["§6.1", "§14", "§29"]
tarefas:
- id: EP-13-T01
titulo: "Agente conversacional de exemplo"
descricao: "Agente LLM com tool-calling restrito ao catálogo retornado por tools/list do client atribuído (demonstra P1–P4 do §1: agente não escolhe entitlement). Instrumentação híbrida conforme ADR-022: span técnico automático por etapa do agente + eventos de negócio manuais e fail-open (IC/NOC/GRL, ver EP-08-T02) — um erro no observer nunca derruba a jornada."
componente: "example_agent"
tipo: feature
prioridade: must
estimativa: M
depende_de: ["EP-05-T02", "EP-05-T04"]
entregaveis: ["agents/example_agent/"]
criterios_aceite: ["Cenário do §29 (prompt injection pedindo finance.createPayment) reproduzido: o agente tenta, o Gateway nega (Teste 3, §45)."]
padroes_abertos: ["MCP"]
usa_langgraph: false
usa_langfuse: true
justificativa_langgraph_langfuse: "Chamadas reais a LLM (tokens, custo, latência) precisam de tracing de generation — critério explícito da seção 6 do superprompt para uso de Langfuse."
riscos: []
rastreabilidade: ["§6.1", "§14", "§29", "ADR-022"]
- id: EP-13-T02
titulo: "Langfuse self-hosted no compose"
descricao: "Deploy da edição OSS do Langfuse (perfil `llm-observability`) com exportação de traces via OTel a partir do agente e do Orchestrator. Stack de referência confirmada em `docs/research/hoshikawa-agent-platform-oci.md` (ADR-022): langfuse-web + langfuse-worker + PostgreSQL + ClickHouse + Redis + MinIO. Habilitar instrumentação automática de chamadas LLM compatíveis com OpenAI (equivalente a `ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION`) para captar tokens/custo/latência sem código manual por chamada."
componente: "example_agent"
tipo: infra
prioridade: must
estimativa: M
depende_de: ["EP-08-T01"]
entregaveis: ["deploy/langfuse/docker-compose.override.yml"]
criterios_aceite: ["Uma execução do agente de exemplo aparece na UI do Langfuse com spans de generation, tokens e latência."]
padroes_abertos: ["OpenTelemetry"]
usa_langgraph: false
usa_langfuse: true
justificativa_langgraph_langfuse: "Infra de suporte à instrumentação definida em EP-13-T01."
riscos: ["[NÃO-OSS]: recursos de billing/RBAC avançado do Langfuse Cloud não são usados — apenas a edição self-hosted/OSS."]
rastreabilidade: ["§6 (seção Langfuse do superprompt)", "ADR-022"]
- id: EP-13-T03
titulo: "Redação de PII/segredos antes do trace"
descricao: "Camada de mascaramento aplicada a prompts/outputs antes de enviar ao Langfuse — obrigatória por regra da seção 6 do superprompt."
componente: "example_agent"
tipo: seguranca
prioridade: must
estimativa: M
depende_de: ["EP-13-T02"]
entregaveis: ["src/emcp_bus/common/pii_redaction.py"]
criterios_aceite: ["Dado um output de tool contendo um e-mail/CPF sintético, quando exportado ao Langfuse, então aparece mascarado no trace."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: true
justificativa_langgraph_langfuse: "Pré-processamento obrigatório antes de qualquer envio a Langfuse."
riscos: []
rastreabilidade: ["§27 ('Não registrar')"]
- id: EP-13-T04
titulo: "Versionamento de prompt no Langfuse"
descricao: "Gestão do system prompt do agente de exemplo via Langfuse Prompt Management, versionado e referenciado por versão nas traces."
componente: "example_agent"
tipo: feature
prioridade: could
estimativa: P
depende_de: ["EP-13-T02"]
entregaveis: ["agents/example_agent/prompts/"]
criterios_aceite: ["Uma mudança de prompt gera nova versão rastreável, sem sobrescrever a anterior."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: true
justificativa_langgraph_langfuse: "Gestão/versionamento de prompts — caso de uso explícito de Langfuse na seção 6 do superprompt."
riscos: []
rastreabilidade: []
- id: EP-13-T05
titulo: "Modo A — Multiagente governado: Global Supervisor externo sobre o E-MCP-BUS"
descricao: "Materializa o modo A do ADR-024 (governança-somente) — a demonstração central de 'elevação a Enterprise' pedida pelo autor. Dois backends de agente distintos (`sales_agent`, `finance_agent`, cada um seu próprio processo/StateGraph, padrão Agent Platform OCI), cada um com seu próprio MCP Client (EP-01) vinculado a um client profile/entitlement diferente (Sales.Read/Write via EP-04 vs. Finance.Read/Payments). Um Global Supervisor **externo** (padrão Agent Gateway do ADR-022, decidindo por conta própria qual backend/NBA usar) roteia a conversa entre os dois backends, incluindo handoff, mas TODA autorização de tools continua exclusivamente no PEP/PDP do E-MCP-BUS — o Global Supervisor nunca decide nem amplia entitlement, e nosso Service Orchestrator (EP-11) não participa da decisão de NBA neste modo."
componente: "example_agent"
tipo: feature
prioridade: should
estimativa: G
depende_de: ["EP-12-T01", "EP-12-T03", "EP-13-T01", "EP-04-T02"]
entregaveis: ["agents/example_agent/backends/sales_agent/", "agents/example_agent/backends/finance_agent/", "src/emcp_bus/agent_runtime/global_supervisor.py"]
criterios_aceite: ["Dado uma conversa iniciada em sales_agent (MCP Client=Sales.Read/Write) que sofre handoff para finance_agent, quando o handoff ocorre, então finance_agent resolve seu próprio MaximumEntitlement (Finance.*) do zero — nunca herda nem amplia o de sales_agent (Axiom 4/9).", "Uma injeção de prompt em sales_agent tentando, via handoff, alcançar finance.payment.execute fora do entitlement de sales_agent é negada pelo Gateway, mesmo que o Global Supervisor tenha roteado a conversa."]
padroes_abertos: ["MCP"]
usa_langgraph: true
usa_langfuse: true
justificativa_langgraph_langfuse: "Orquestração multiagente com handoff entre backends distintos (padrão Global Supervisor do ADR-022) e necessidade de correlacionar em uma única trace toda a jornada cross-backend (Langfuse) — critérios explícitos da seção 6 do superprompt para ambos."
riscos: ["Escopo maior que um agente único; não bloqueia M0-M2. É a tarefa que melhor demonstra o valor 'Enterprise' do bus (ADR-024) — priorizar dentro de M3/M4 mesmo não sendo `must`."]
rastreabilidade: ["ADR-024", "§18 (shared client rule)", "§31 (blast-radius containment)", "Axiom 9"]
- id: EP-13-T06
titulo: "Modo B — Service Orchestrator como único cérebro de NBA/NBO"
descricao: "Materializa o modo B do ADR-024 (substituição completa): a mesma cena de dois backends (Sales/Finance) de EP-13-T05, mas agora o Global Supervisor externo é dispensado — nosso próprio Service Orchestrator (EP-11-T03) calcula a NBA/NBO diretamente sobre `FilteredOfferings` combinando ofertas de Sales e Finance (ex.: apresentar um benefício vs. cobrar uma pendência), e o Agent Runtime (EP-12) apenas despacha o resultado para o backend/canal indicado — os backends de agente tornam-se executores puros, sem lógica própria de decisão de qual agir. Demonstra que a mesma stack de segurança (EP-01/EP-03/EP-05) não muda entre os dois modos, apenas quem decide a NBA/NBO."
componente: "example_agent"
tipo: feature
prioridade: could
estimativa: M
depende_de: ["EP-11-T03", "EP-13-T05"]
entregaveis: ["agents/example_agent/mode_b_orchestrator_driven/"]
criterios_aceite: ["Dado o mesmo cenário de EP-13-T05 (subject elegível a um benefício de Sales e com pendência em Finance), quando rodado no modo B, então a NBA/NBO escolhida e o backend acionado vêm do nosso Service Orchestrator, não de um Global Supervisor externo.", "O conjunto de testes de segurança de EP-15-T02 passa igualmente nos modos A e B, sem nenhuma alteração no PEP/PDP entre eles."]
padroes_abertos: []
usa_langgraph: true
usa_langfuse: true
justificativa_langgraph_langfuse: "Mesmo grafo de jornada de EP-11-T02 agora dirigindo diretamente múltiplos backends de execução; Langfuse correlaciona a decisão única do Orchestrator com a execução em qualquer um dos backends."
riscos: ["Prioridade `could` — demonstra a modularidade do ADR-024, mas não é necessária para nenhum critério de aceite de segurança; pode ser adiada sem risco para o núcleo da RI."]
rastreabilidade: ["ADR-024", "RF-15"]
- epico_id: EP-14
titulo: "Human-in-the-loop / Approval workflow"
objetivo: "Aprovação adicional para operações de alto risco, fora da influência exclusiva do LLM (§26)."
secoes_readme: ["§26", "§17 (R3/R4)"]
tarefas:
- id: EP-14-T01
titulo: "Contrato de aprovação"
descricao: "Objeto de aprovação out-of-band, criptograficamente associado à operação, com prazo de validade e single-use (§26)."
componente: "approval"
tipo: feature
prioridade: must
estimativa: M
depende_de: ["EP-03-T04"]
entregaveis: ["schemas/approval-request.schema.json", "src/emcp_bus/approval/models.py"]
criterios_aceite: ["Uma aprovação usada duas vezes na segunda tentativa é rejeitada (single-use, Threat: approval replay, §46)."]
padroes_abertos: ["JSON Schema"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-10"]
- id: EP-14-T02
titulo: "Serviço de aprovação"
descricao: "API/CLI mínima que gate a execução de REQUIRE_APPROVAL do PDP/Orchestrator, com resumo dos argumentos relevantes exibido ao aprovador."
componente: "approval"
tipo: feature
prioridade: must
estimativa: M
depende_de: ["EP-14-T01", "EP-11-T05"]
entregaveis: ["src/emcp_bus/approval/service.py"]
criterios_aceite: ["Dado payment.execute >= 10k, quando solicitado, então a execução só prossegue após aprovação explícita registrada (§26 exemplo)."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-10", "§26"]
- id: EP-14-T03
titulo: "Fail-closed do serviço de aprovação"
descricao: "Se o serviço de aprovação estiver indisponível, toda operação high-risk é negada, nunca liberada por omissão (§38)."
componente: "approval"
tipo: seguranca
prioridade: must
estimativa: P
depende_de: ["EP-14-T02"]
entregaveis: ["src/emcp_bus/approval/failure_policy.py"]
criterios_aceite: ["Dado o serviço de aprovação fora do ar, quando uma R3/R4 é solicitada, então DENY, não timeout silencioso."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§38"]
- epico_id: EP-15
titulo: "Testes de segurança e adversariais"
objetivo: "Validar formalmente os 10 critérios de aceite de segurança (§45) e os cenários adversariais (§46) contra a stack completa."
secoes_readme: ["§45", "§46", "§28"]
tarefas:
- id: EP-15-T01
titulo: "Testes de contrato dos MCP Servers"
descricao: "Testes que validam cada tool exposta contra o input/output schema declarado em seu manifesto (EP-02)."
componente: "tests"
tipo: teste
prioridade: must
estimativa: M
depende_de: ["EP-06-T02", "EP-06-T03", "EP-02-T05"]
entregaveis: ["tests/contract/test_sales_domain.py", "tests/contract/test_finance_domain.py"]
criterios_aceite: ["Uma resposta de tool que viola seu output schema falha o teste de contrato."]
padroes_abertos: ["JSON Schema"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["RF-06"]
- id: EP-15-T02
titulo: "Suíte de aceite de segurança (Testes 1–10, §45)"
descricao: "Automação pytest dos 10 testes formais do §45 rodando contra o docker-compose completo."
componente: "tests"
tipo: teste
prioridade: must
estimativa: G
depende_de: ["EP-05-T02", "EP-05-T03", "EP-05-T04", "EP-05-T07", "EP-01-T03", "EP-04-T03"]
entregaveis: ["tests/e2e/test_security_acceptance.py"]
criterios_aceite: ["Os 10 testes do §45 (unauthorized discovery, direct unauthorized call, prompt injection, role spoofing, cache isolation, policy revocation, gateway bypass, token audience, backend isolation, client compromise) passam de ponta a ponta."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: ["Suíte grande — dividir execução por marco em vez de tentar completar tudo de uma vez"]
rastreabilidade: ["§45", "todos os RF de segurança"]
- id: EP-15-T03
titulo: "Testes adversariais de prompt/tool injection"
descricao: "Cenários do §46: direct/indirect prompt injection, malicious RAG chunk, tool output injection, tool description poisoning, hidden tool invocation, tool name spoofing. Inclui um cenário adicional derivado do achado do ADR-023 (payload-declared identity spoofing): forjar `agent_id`/`tenant_id`/`business_context` no payload de entrada do canal, tentando `tools/call` de uma capability fora do MaximumEntitlement do MCP Client real — variação prática do Teste 4 (§45, agent role spoofing), motivada por uma vulnerabilidade real observada em pesquisa de referência (docs/research/hoshikawa-agent-platform-oci.md)."
componente: "tests"
tipo: teste
prioridade: must
estimativa: G
depende_de: ["EP-13-T01"]
entregaveis: ["tests/adversarial/test_prompt_injection.py", "tests/adversarial/test_tool_poisoning.py", "tests/adversarial/test_payload_identity_spoofing.py"]
criterios_aceite: ["Nenhum cenário simulado resulta em EffectiveCapabilities excedendo ClientEntitlement (Axiom 4).", "Um `agent_id`/`tenant_id` forjado no payload de entrada nunca altera a identidade usada pelo PDP para resolver entitlement (ADR-023)."]
padroes_abertos: ["OWASP GenAI LLM01"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§14", "§28", "§46", "ADR-023"]
- id: EP-15-T04
titulo: "Testes adversariais de protocolo/identidade"
descricao: "Cenários do §46: replay, stolen/expired/wrong-audience token, header/body mismatch, shared-cache poisoning, entitlement race condition, approval replay, bulk exfiltration via tools legítimas."
componente: "tests"
tipo: teste
prioridade: must
estimativa: G
depende_de: ["EP-05-T08", "EP-05-T03", "EP-14-T01", "EP-05-T05"]
entregaveis: ["tests/adversarial/test_protocol_identity.py"]
criterios_aceite: ["Cada um dos 8 cenários resulta em DENY/rejeição determinística, sem exceção não tratada."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§46", "§28"]
- id: EP-15-T05
titulo: "Testes de política de falha (fail-closed)"
descricao: "Chaos tests derrubando PDP/IdP/Registry/Approval individualmente e validando o comportamento esperado da tabela do §38."
componente: "tests"
tipo: teste
prioridade: must
estimativa: M
depende_de: ["EP-05-T06", "EP-14-T03"]
entregaveis: ["tests/e2e/test_fail_closed.py"]
criterios_aceite: ["Para cada componente da tabela §38, o comportamento observado corresponde exatamente ao recomendado."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: ["§38", "RNF-Disponibilidade"]
- epico_id: EP-16
titulo: "Deploy final e documentação"
objetivo: "Consolidar a stack completa, ADRs e documentação de uso da RI."
secoes_readme: ["Todo o documento — integração final"]
tarefas:
- id: EP-16-T01
titulo: "docker-compose completo"
descricao: "Expandir EP-00-T04 para incluir todos os serviços (Keycloak, OPA, Registry store, Gateway, Fabric, servidores de domínio, Orchestrator, Agent Runtime, agente de exemplo, OTel/Jaeger, Langfuse) com perfis coerentes."
componente: "deploy"
tipo: infra
prioridade: must
estimativa: M
depende_de: ["EP-05-T08", "EP-11-T05", "EP-12-T02", "EP-13-T04", "EP-14-T03", "EP-15-T05"]
entregaveis: ["RI/docker-compose.yml (final)"]
criterios_aceite: ["`docker compose up` sobe a stack completa e a suíte EP-15-T02 passa contra ela."]
padroes_abertos: ["OCI"]
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: []
- id: EP-16-T02
titulo: "README da RI"
descricao: "Quickstart, mapa de volta para as seções do README de arquitetura, como rodar cada suíte de testes."
componente: "docs"
tipo: docs
prioridade: must
estimativa: P
depende_de: ["EP-16-T01"]
entregaveis: ["RI/README.md"]
criterios_aceite: ["Uma pessoa nova consegue subir a stack e rodar a suíte M0 seguindo apenas o README da RI."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: []
- id: EP-16-T03
titulo: "ADRs formalizados"
descricao: "Um arquivo por ADR (ADR-001..021 da seção 8.4 deste documento) em docs/adr/, no formato Contexto/Decisão/Alternativas/Consequências."
componente: "docs"
tipo: docs
prioridade: should
estimativa: M
depende_de: ["EP-16-T01"]
entregaveis: ["docs/adr/ADR-001-*.md ... ADR-021-*.md"]
criterios_aceite: ["Todos os 21 ADRs listados na seção 8.4 deste documento existem como arquivo individual."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: []
rastreabilidade: []
- id: EP-16-T04
titulo: "Spike: verificação de versões estáveis"
descricao: "Confirmar, imediatamente antes de fixar em pyproject.toml, os números exatos de versão estável de: SDK `mcp` (Python v2), LangGraph, Langfuse (edição OSS), OPA (v1.x, sintaxe Rego v1), Keycloak. Parcialmente pré-verificado em 2026-09-11 via busca web: a revisão MCP 2026-07-28 é real e GA (não é invenção do README); o SDK Python `mcp` v2 já suporta nativamente `cache_hints`/ttlMs/cacheScope (SEP-2549) e os headers Mcp-Method/Mcp-Name (SEP-2243); OPA está em v1.0+ com Rego v1 como sintaxe padrão. Falta apenas fixar os números de patch/minor exatos no pyproject.toml no momento da implementação."
componente: "docs"
tipo: spike
prioridade: must
estimativa: P
depende_de: []
entregaveis: ["docs/adr/ADR-018-version-pins.md (registro das versões confirmadas e data da verificação)"]
criterios_aceite: ["Nenhuma versão é citada em código/config sem ter sido confirmada nesta tarefa."]
padroes_abertos: []
usa_langgraph: false
usa_langfuse: false
justificativa_langgraph_langfuse: ""
riscos: ["Sem esta tarefa, há risco de alucinação de versão — por isso priorizada `must` e sem dependências, executável a qualquer momento."]
rastreabilidade: ["Regra 4 do superprompt (sem alucinação de versões)"]
8.7 Sequenciamento e marcos
| Marco | Tarefas | Critério de "pronto" |
|---|---|---|
| M0 — Walking skeleton ✅ concluído (2026-09-11, revalidado 2026-09-14) | EP-00-T01, EP-00-T02, EP-00-T04, EP-06-T02, EP-05-T01, EP-08-T01 | Cliente MCP → Gateway → (roteamento estático) → MCP Server de exemplo (Sales) → resposta, com trace_id correlacionado visível em log/coletor OTel. Sem enforcement real ainda. Critério satisfeito desde a implementação inicial; hoje o roteamento deixou de ser estático (EP-06-T01, ver M1), mas o próprio trace correlacionado do walking skeleton continua coberto por tests/e2e/test_walking_skeleton.py. |
| M1 — Segurança núcleo (Fase 1 do README) ✅ concluído (2026-09-11, revalidado 2026-09-14) | EP-01 completo, EP-03 completo, EP-04 completo, EP-05-T02/T03/T04/T06/T07/T08, EP-02-T01/T02/T03/T05 (via M2), EP-06-T01/T03/T04, EP-08-T02/T03/T04 | tools/list filtrado por entitlement real (Keycloak+OPA), tools/call reautorizado, cache privado, fail-closed, anti-bypass, auditoria completa. Cobre RF-01 a RF-09 e Fases 0–2 do roadmap do README. Adendo (2026-09-11, fechamento retroativo pós-M2): todos os itens acima que ainda restavam abertos após o M2 (EP-01-T04/T05, EP-05-T03/T07/T08, EP-06-T01/T03/T04, EP-08-T02/T03/T04) foram implementados nesta rodada — Fabric router real (multi-backend, EP-06-T01), segundo domínio Finance com backend real (EP-06-T03), adapter REST genérico standalone (EP-06-T04), taxonomia de auditoria IC/NOC/GRL + correlação decisionId/policyDecisionId/entitlementVersion (EP-08-T02/T03), stack local otel-collector+Jaeger (EP-08-T04), cache hint SEP-2549 (EP-05-T03 — ver limitação de wire-mode abaixo), segmentação de rede + gate de credencial nos domain servers (EP-05-T07), validação header/body Mcp-Method/Mcp-Name (EP-05-T08), lint de shared client (EP-01-T05), spike de mTLS documentado em docs/adr/ADR-019-identity-extension.md (EP-01-T04). Limitação real descoberta e documentada: SEP-2549 (ttlMs) é vocabulário exclusivo do protocolo 2026-07-28, que só existe no modo stateless per-request do SDK (server/discover, sem handshake initialize) — esta RI usa o handshake de sessão clássico em toda parte, então o campo nunca chega ao wire nesta arquitetura (o SDK descarta o campo na serialização para versões de protocolo anteriores); cache_scope="private" e o não-caching server-side já garantem a propriedade de segurança do Teste 5 independentemente disso. Ver src/emcp_bus/gateway/cache.py. EP-00-T03 (pipeline de CI), apesar de não listado nesta linha, também foi implementado (.github/workflows/ci.yml). Adendo (2026-09-14, revalidação de prontidão): reinstalação limpa do .venv (3 dias depois, mesmo ambiente) + make lint/make typecheck/make validate-schemas/make export-schemas (sem diff) + suíte completa --forked → 119/119 testes passando (97 unit + 22 e2e), sem nenhuma falha — confirma que o commit f3efcc3 ("M0 e M1 revisados e fixes implementados") capturou exatamente o estado validado na sessão anterior, sem drift. |
| M2 — Governança e downstream ✅ concluído (2026-09-11, revalidado 2026-09-14) | EP-02 completo (T01-T05, incluindo T02/T03/T05 adiados do M1), EP-04-T04, EP-07 completo, EP-14 completo | Publishing pipeline com lifecycle completo, outbound identity sem passthrough, aprovação humana funcionando fail-closed — todos ligados ao Gateway real (EP-05) e testados com OPA real. Cobre RF-10, RF-11, Fase 3 parcial. Detalhes e limitações em RI/README.md. Adendo (2026-09-11, mesmo dia — Docker e Java passaram a estar disponíveis no ambiente): docker-compose.yml validado com daemon Docker real (build/up/fail-closed/trace cross-container), e EP-01-T01 (Keycloak) implementado e validado com Keycloak 26.7.3 real (deploy/keycloak/realm-export.json, perfil identity no compose, tests/e2e/test_real_keycloak.py) — item que a linha do M1 acima já citava como pré-requisito ("Keycloak+OPA") mas que só foi de fato implementado/testado agora. Ciclo de aprovação (EP-14) hoje testado e2e tanto contra Sales quanto contra Finance (EP-06-T03, ver M1). |
| M3 — Personalização e orquestração (Fase 4 do README) ✅ concluído (2026-09-14) | EP-09, EP-10, EP-11, EP-12, EP-13-T01..T04 | Perfil→entitlement→ofertas filtradas→NBA→dispatch→execução MCP funcionando ponta a ponta com LangGraph no Orchestrator e Langfuse no agente de exemplo. Cobre RF-12 a RF-17. EP-09 (Profile Intelligence) ✅ — src/emcp_bus/profile_intelligence/{models,rule_based_provider,governance}.py: contrato ProfileView (T01), provider determinístico rule-based (T02), guardrails de governança — pseudonimização, lineage log, stub de drift monitor (T03). EP-10 (Offering Filter) ✅ — src/emcp_bus/offering_filter/service.py: FilteredOfferings = ActiveCatalog ∩ MaximumEntitlement ∩ ConsentContext (T01); invariante de subconjunto provada com Hypothesis, 2000 casos gerados (T02). EP-11 (Service Orchestrator) ✅ — src/emcp_bus/orchestrator/*.py: DecisionContext versionado (T01); StateGraph real do LangGraph 0.6 com checkpointer SQLite, nós fixos resolve_profile→resolve_entitlement→filter_offerings→decide_nba→approval_gate (T02); NBADecisionModel pluggable + referência determinística (T03); recálculo/degradação após DENY como edge condicional, nunca chamando o Fabric diretamente (T04); interrupção human-in-the-loop via langgraph.types.interrupt/Command(resume=...), integrada a EP-14 (T05) — validado contra OPA real + LangGraph real + checkpointer SQLite real, incluindo prova de que retomar de um checkpoint pausado não reprocessa nós já executados. EP-12 (Agent Runtime) ✅ — src/emcp_bus/agent_runtime/*.py: serviço de dispatch que nunca gera/armazena credencial própria, usa exclusivamente o MCP Client já registrado via client_credentials real (T01); channel adapter conversacional + registro com falha explícita para canal não suportado (T02); handoff com minimização de contexto e resolução de entitlement sempre independente por agente (T03, could); Identity Resolver/BusinessContext declarativo (config/orchestrator/identity.yaml), nunca consultado pelo PDP — checado mecanicamente via AST (T04). Validado com Keycloak real + Gateway real: AgentDispatcher alcançando o backend Sales real e sendo negado no cross-domain, e dois agentes reais (sales-read-agent/finance-payments-agent) obtendo tokens client_credentials independentes. EP-13-T01..T04 (Agente de exemplo + Langfuse) ✅ — agents/example_agent/: agente conversacional com tool-calling real (endpoint OpenAI-compatible da OCI Generative AI, meta.llama-3.3-70b-instruct, tool-calling confirmado empiricamente 2026-09-14), catálogo restrito ao tools/list do próprio client — cenário de prompt injection do §29 reproduzido de verdade contra o Gateway/PDP reais (T01); Langfuse self-hosted real (deploy/langfuse/docker-compose.override.yml, adaptado do compose oficial upstream, perfil llm-observability opt-in) — subida de verdade e validada: 6 containers (web/worker/postgres/clickhouse/redis/minio) saudáveis, spans de MCP e de generation (com gen_ai.request.model/tokens reais) confirmados fisicamente na tabela events_core do ClickHouse via o endpoint OTLP real do Langfuse (/api/public/otel/v1/traces) — validação manual documentada em RI/README.md, não um teste e2e automatizado (custo de subir 6 containers a cada run) (T02); redação de PII/segredos antes de qualquer export (src/emcp_bus/common/pii_redaction.py, T03); versionamento de prompt local (agents/example_agent/prompts/store.py) como núcleo testável do "nunca sobrescreve versão anterior" — sync real para a API de Prompt Management do Langfuse documentado como adapter fino não implementado nesta sessão (T04, could). Dependências novas: langgraph, langgraph-checkpoint-sqlite, hypothesis, openai, python-dotenv. 201 testes automatizados no total (171 unit + 30 e2e, todos passando com --forked, incluindo os novos testes reais de LLM/Keycloak/OPA/LangGraph do M3 — nenhum mock no núcleo de segurança ou orquestração). |
| M4 — Hardening e entrega ✅ concluído (2026-09-14) | EP-13-T05, EP-13-T06 (could), EP-15 completo, EP-16 completo | Suíte de 10 testes de aceite (§45) e cenários adversariais (§46, incluindo o de EP-15-T03/ADR-023) passando contra a stack completa; os dois modos de integração do ADR-024 demonstrados — Modo A (EP-13-T05: Global Supervisor externo, dois backends/MCP Clients, handoff sem elevação de privilégio) e Modo B (EP-13-T06: nosso Service Orchestrator como único cérebro de NBA/NBO, mesma stack de segurança); ADRs documentados; README da RI publicado. Todas as tarefas concluídas, incluindo as duas could (EP-13-T05/T06) — nenhum item do backlog de M0-M4 permanece aberto. Detalhamento tarefa-a-tarefa: EP-15-T01/T02 ✅ (ver adendo de 2026-09-14 abaixo, sessão anterior a esta). EP-15-T03 (testes adversariais de prompt/tool injection) ✅ — tests/adversarial/{test_prompt_injection,test_tool_poisoning,test_payload_identity_spoofing}.py, 12 testes novos contra o Gateway real (governed_stack/governed_stack_with_finance): injeção direta/indireta de conteúdo em argumentos nunca altera autorização nem o catálogo (tools/list idêntico antes/depois de processar conteúdo adversarial); hidden tool invocation e spoofing de nome de tool (case/whitespace/typo) sempre negados por comparação exata; tool description poisoning provado com um MCP server malicioso real montado em runtime (MCPServer in-process, descrição hostil pedindo "acesso admin"), confirmando que a Gateway repassa a descrição verbatim (nunca a sanitiza) mas ela não tem nenhum efeito na decisão PDP; variação explícita do achado ADR-023 (payload-declared agent_id/tenant_id/business_context forjados nos argumentos de tools/call) provada tanto por negação de entitlement quanto por auditoria (o evento tool_call_allowed sempre registra o client_id real, nunca o forjado). Achado de dados: CapabilityManifest não tem campo description (mesmo achado do EP-15-T01) — "tool description poisoning" é portanto um vetor de domain server, não de Registry, documentado explicitamente no docstring do arquivo. EP-15-T04 (testes adversariais de protocolo/identidade) ✅ — tests/adversarial/test_protocol_identity.py, 6 testes novos + referências cruzadas explícitas para os cenários já cobertos alhures (expired/wrong-audience/stolen token em test_governed_gateway.py/test_authn.py/test_security_acceptance.py Teste 10; approval replay sequencial já existente): header/body mismatch rejeitado antes da autenticação (POST cru com Mcp-Method divergente do body); replay de requisição byte-idêntica não é bloqueado (gap real e documentado — sem nonce/anti-replay nesta RI; mitigado por token de vida curta) mas cada tentativa gera decision_id distinto no audit trail; revogação de política ao vivo contra um Gateway real rodando (fecha a lacuna que o EP-15-T02 havia documentado para o Teste 6 — sem endpoint HTTP de revogação, exercitado diretamente via EntitlementManager.load_profiles/revoke_profile + PDPClient.push_client_profile, os mesmos primitivos que tal endpoint chamaria); condição de corrida de entitlement sob concorrência real (asyncio.gather) nunca resulta em crash ou resultado inconsistente; approval replay sob concorrência real (ThreadPoolExecutor, 20 threads) prova atomicidade de ApprovalService.check_and_consume — achado real corrigido: o método não era thread-safe (check-then-set em duas operações não atômicas), corrigido com um threading.Lock dedicado; bulk exfiltration via tools de leitura legítimas não é bloqueado (gap real e documentado — sem rate limiting nesta RI) mas cada chamada gera evento de auditoria com decision_id próprio, provando reconstrutibilidade completa a posteriori. EP-15-T05 (testes de política de falha) ✅ — tests/e2e/test_fail_closed.py, um teste por linha da tabela do §38, incluindo cenários que EP-05-T06/test_governed_gateway.py não cobriam (PDP/IdP indisponíveis em runtime, não só no boot): IdP inalcançável nega novas autenticações; PDP morrendo em pleno funcionamento (processo OPA descartável morto no meio do teste) faz a próxima chamada falhar fechado (PDP_UNAVAILABLE); Registry sem capabilities/ para semear falha o boot do Gateway (FileNotFoundError, mesma postura do PDP inalcançável); achado real corrigido: nenhum dos 8 audit_sink.emit(...) do Gateway estava protegido — uma falha no sink (disco cheio, fila indisponível) teria virado erro 500 para uma operação de baixo risco já ALLOWed, violando "não bloquear low-risk"; corrigido com o helper _emit_audit (src/emcp_bus/gateway/server.py), que engole qualquer exceção do sink em todos os 8 pontos de emissão, para ALLOW e para DENY; approval service falho nega (nunca crasha nem libera) uma operação de alto risco, provando ao vivo pela primeira vez o except Exception: approved = False do EP-14-T03; backend morto falha só aquela requisição, sem derrubar o processo do Gateway (um client não-relacionado, entitled a um backend saudável diferente, não é afetado). EP-13-T05 (Modo A, should) ✅ — src/emcp_bus/agent_runtime/global_supervisor.py + agents/example_agent/backends/{sales_agent,finance_agent}/: GlobalSupervisor com roteamento por palavra-chave simples (deliberadamente não-inteligente — não é a fronteira de segurança, documentado explicitamente), nunca importa emcp_bus.orchestrator (checado via AST, mesma disciplina do EP-12-T04) — prova mecânica de que o Modo A realmente não usa o Orchestrator/NBA próprio. Validado com LLM real + Gateway real + Keycloak-stand-in real (tests/e2e/test_multi_agent_mode_a.py, 2 testes): (1) handoff de sales_agent para finance_agent — finance_agent resolve seu próprio tools/list/entitlement do zero, com token Bearer completamente distinto do de sales_agent (Axiom 4/9); (2) uma injeção de prompt tentando, via handoff, forçar finance.payment.execute fora de aprovação continua negada pelo Gateway/PDP reais, mesmo com o roteamento tendo migrado a conversa para o backend certo. EP-13-T06 (Modo B, could) ✅ — agents/example_agent/mode_b_orchestrator_driven/{decision_model,orchestrator}.py: ModeBOrchestrator invoca o journey StateGraph real do EP-11-T02 (não modificado) uma vez por client_id relevante para o mesmo subject, combina os NBADecisions resultantes (regra de referência: pendência de Finance sempre vence oferta discricionária de Sales) e despacha via AgentDispatcher real (EP-12-T01) — nunca cria um caminho de autorização próprio. Validado contra Keycloak real, OPA real, Sales+Finance reais e o Gateway real (tests/e2e/test_real_keycloak.py::test_mode_b_orchestrator_combines_offerings_and_dispatches_through_the_real_chain): a mesma subject realmente produz candidatos de AMBOS os domínios (prova de combinação real, não apenas um domínio rodando sozinho), a decisão final vem do combinador do nosso Orchestrator (nunca de um Global Supervisor externo), e o dispatch é ALLOW de ponta a ponta. Decisão de design registrada: ModeBOrchestrator não estende JourneyState/build_journey_graph para abranger múltiplos client_ids nativamente (arriscaria um componente já testado por um demo could) — a combinação vive uma camada acima do grafo, exatamente onde o próprio docstring do AgentDispatcher já dizia que a orquestração pós-decisão pertence. EP-16-T01 (docker-compose completo) ✅ — validado com docker compose --profile core --profile identity --profile observability up -d --build real: todos os 7 serviços sobem limpos (Keycloak, OPA, Sales, Finance, Gateway, OTel Collector, Jaeger), healthchecks passam, Gateway inicia só depois de Sales/Finance saudáveis; a suíte completa do EP-15-T02 (7 testes) já passa contra o mesmo compose (core+identity). Agente de exemplo/Orchestrator/Agent Runtime permanecem bibliotecas Python consumidas por processo (Gateway ou scripts de teste/demo), não serviços containerizados próprios — decisão de escopo deliberada: esta RI demonstra propriedades de segurança, não uma UI de chat pronta para produção. EP-16-T02 (README da RI) ✅ — ver RI/README.md, seções M4/"O que existe hoje"/"Limitações conhecidas" atualizadas nesta rodada. EP-16-T03 (ADRs formalizados) ✅ — 25 arquivos em docs/adr/ (ADR-001 a ADR-025, um a mais que os 21 originalmente previstos: a tabela §8.4 cresceu para 25 entradas durante o M3/M4 com a pesquisa Hoshikawa, ADR-022 a ADR-025), gerados mecanicamente a partir da própria tabela da seção 8.4 (fonte única, evita divergência) no formato Contexto/Decisão/Alternativas/Consequências; ADR-019 já existia como o spike aprofundado do EP-01-T04 e foi preservado como está. EP-16-T04 (versões confirmadas) ✅ — registro consolidado adicionado como seção final de docs/adr/ADR-018-stack-de-implementacao.md (não um arquivo ADR-018-version-pins.md separado como o texto original da tarefa sugeria — colidiria com o ADR-018 real da tabela §8.4, "Stack de implementação"; resolvido fundindo o conteúdo no mesmo arquivo em vez de criar uma segunda contagem "ADR-018"). Correções de código reais feitas durante a validação do M4 (não apenas testes novos): (1) ApprovalService ganhou um threading.Lock — sem ele, check_and_consume tinha uma race condition real de double-spend sob concorrência de threads reais; (2) _emit_audit no Gateway — nenhuma das 8 chamadas a audit_sink.emit estava protegida contra falha do sink, violando §38 para o caso ALLOW. Ambas descobertas escrevendo os testes adversariais/fail-closed, não hipotéticas. Suíte final: 260 testes automatizados no total — 253 passando com --forked (confirmado nesta rodada, todo o tests/ exceto test_security_acceptance.py) mais os 7 de EP-15-T02 contra o Docker real (rodados separadamente pelo custo de build+Keycloak por execução) — ver RI/README.md para a composição exata por categoria. |
Caminho crítico: EP-00 → EP-01/EP-06 (paralelos) → EP-05 (núcleo do PEP) → EP-02/EP-03/EP-04 (entitlement real) → EP-08 (correlação) → EP-09/EP-10 → EP-11 (Orchestrator) → EP-12/EP-13 → EP-15 (validação) → EP-16 (entrega). EP-07 e EP-14 podem ser paralelizados a partir de M1 sem bloquear o caminho principal até M3.
Nível de prontidão para M4 — histórico da avaliação inicial (2026-09-14, atualizado após conclusão do M3; ver a linha do M4 na tabela de marcos acima para o estado final, M4 100% concluído)
M0, M1, M2 e M3 estão concluídos (ver linha do M3 na tabela acima para o detalhamento tarefa-a-tarefa — EP-09 a EP-12 completos, EP-13-T01..T04 completos). Caminho crítico até M4 (... → EP-11 → EP-12/EP-13 → EP-15 (validação) → EP-16 (entrega)): EP-11 (Orchestrator, LangGraph real) e EP-12/EP-13 (Agent Runtime + agente de exemplo) — os itens que o caminho crítico original citava como pré-requisito do M4 — estão implementados e testados contra dependências reais (OPA, Keycloak, LangGraph, LLM, Langfuse). Não há bloqueio técnico para iniciar o M4 (EP-13-T05/T06, EP-15, EP-16).
Ressalvas não-bloqueantes específicas do M3 (ver RI/README.md, seção "M3 — Personalização e orquestração" e "Limitações conhecidas", para o detalhamento completo): EP-12-T03 (handoff) tem o primitivo cross-process pronto mas nenhum subgrafo LangGraph intra-processo ainda (não há dois agentes ativos simultâneos até o EP-13-T05); EP-13-T02 (Langfuse) foi validado manualmente de verdade, não por teste e2e automatizado; EP-13-T04 (prompt versioning) tem o núcleo local pronto mas não sincroniza com a API real do Langfuse.
Progresso do M4 (2026-09-14) — EP-15-T01 (testes de contrato) ✅ concluído: tests/contract/{conftest,test_sales_domain,test_finance_domain}.py, conectando direto a cada domain server (nunca pelo Gateway — o contrato input/output de uma tool é propriedade do domain server, independente de quem está ou não entitled a chamá-la, ADR-023). Achado de dados durante a implementação: CapabilityManifest (EP-02-T01) não carrega input_schema/output_schema — o desenho original do EP-02 nunca incluiu isso (o papel do manifesto é metadado de entitlement/risco, não formato). Resolvido de forma honesta e verificada empiricamente contra o servidor Sales real: (a) o input_schema que o MCP SDK auto-gera a partir da assinatura Python de cada tool é real, específico por campo, e de fato validado no servidor (campo obrigatório ausente ou tipado errado é rejeitado via Pydantic antes de qualquer código da RI rodar) — reaproveitado diretamente, não reimplementado; (b) o output_schema auto-gerado é genérico demais (additionalProperties: true, já que toda tool retorna dict[str, object]) para servir de contrato — resolvido com JSON Schema escrito à mão por tool (biblioteca jsonschema, já dependência transitiva), validado contra a resposta real de cada uma das 9 tools (6 Sales + 3 Finance). 20 testes novos (6 positivos de schema + 1 parametrizado de campo ausente + 1 de tipo errado, por domínio). EP-15-T02 (suíte de aceite de segurança, §45) ✅ concluído: tests/e2e/test_security_acceptance.py, rodando contra o docker-compose.yml real (--profile core --profile identity, Keycloak real incluído) — não os fixtures in-process do resto da suíte e2e. 7 dos 10 testes formais automatizados diretamente contra o stack: Teste 1 (unauthorized discovery), Teste 2 (direct unauthorized call), Teste 4 (role spoofing via campos forjados no payload), Teste 5 (cache isolation, incluindo asserção de cache_scope="private"), Teste 7 (gateway bypass — confirma que sales-domain/finance-domain/opa não publicam porta de host), Teste 9 (backend/transaction-policy isolation — TRANSACTION_POLICY_DENIED real do Rego), Teste 10 (client compromise containment — token válido de sales-read-agent não escapa do seu próprio MaximumEntitlement). Os outros 3 têm cobertura real já existente, documentada no docstring do arquivo em vez de duplicada: Teste 3 (prompt injection) já é coberto com LLM real em tests/e2e/test_example_agent.py; Teste 6 (policy revocation) não tem endpoint HTTP de revogação nesta RI (EntitlementManager.revoke_client/revoke_profile são métodos in-process) — coberto em tests/unit/test_entitlement_manager.py; Teste 8 (token audience) já é provado contra o mesmo realm Keycloak real em tests/e2e/test_real_keycloak.py. Suíte completa revalidada após as duas tarefas: 228 testes automatizados no total — 221 (171 unit + 30 e2e + 20 contract) passando com --forked em ~6.5min, mais os 7 de EP-15-T02 confirmados separadamente contra o Docker real (~72s, não incluídos na rodada --forked padrão dado o custo de build+Keycloak por execução).
Nível de prontidão para M3/M4 — avaliação histórica, antes do início do M3 (2026-09-14)
M0, M1 e M2 estão 100% concluídos e revalidados — nenhuma tarefa aberta nos três marcos (auditoria tarefa-a-tarefa contra os entregáveis reais em disco, feita nesta revisão: todo entregavel de EP-00 a EP-08 e EP-14 existe e está funcional). Especificamente para o caminho crítico até M3 (EP-06 → EP-05 → EP-02/03/04 → EP-08 → EP-09/10, ver acima): EP-06-T01 (Fabric router multi-backend) e EP-06-T03 (segundo domínio real, Finance) — os dois itens que este mesmo documento identificou como bloqueio técnico real do M3 — estão implementados e testados, assim como EP-08-T02/T03 (auditoria/correlação), também pré-requisito citado. Não há bloqueio técnico remanescente para iniciar o M3.
Únicas ressalvas, nenhuma bloqueante:
- EP-06-T04 (adapter REST) implementado e testado apenas de forma standalone — nenhum backend de exemplo desta RI é REST, então não há um caminho ao vivo através do Gateway exercitando-o. Só passa a importar se um EP-09+ precisar de um backend não-MCP-nativo.
- EP-08-T04 (stack de observabilidade) tem a config validada (
docker compose config) mas não foi exercitada com tráfego OTLP real end-to-end. - A flakiness de teste e2e que sessões anteriores documentaram como um risco de credibilidade da suíte está resolvida (
pytest-forked, 119/119 testes reprodutíveis) — deixa de ser um risco a carregar para M3/M4.
Adendo (2026-09-14, resolvido durante o início do M3, a pedido do autor): Token exchange RFC 8693 (modo token_exchange de BackendConfig, dentro de EP-07) deixou de ser um NotImplementedError — implementado em src/emcp_bus/downstream/identity.py (TokenExchangeClient) contra o Standard Token Exchange do Keycloak (GA desde 26.2, sem feature flag de preview). Validado empiricamente contra um Keycloak 26.7.3 real (não apenas suposto pela doc): (a) o parâmetro audience do exchange exige um client id realmente registrado no realm — uma string livre ("custom audience") falha com "Audience not found"; (b) pedir audience sem também pedir, via scope, um client scope que efetivamente adiciona essa audience falha com "Requested audience not available" (o parâmetro audience só restringe, nunca adiciona). deploy/keycloak/realm-export.json ganhou o client gateway-token-exchange (standard.token.exchange.enabled: true), dois clients placeholder somente-audiência (sales-domain-service/finance-domain-service, nunca usados para login) e dois client scopes (aud-sales-domain-service/aud-finance-domain-service) com oidc-audience-mapper. BackendConfig ganhou token_exchange_scope (novo campo obrigatório no modo token_exchange). credential_for/CapabilityRouter.route tornaram-se async def (a troca de token é uma chamada de rede) — refletido em todo o Gateway. 7 novos testes (4 unit com httpx.MockTransport simulando o fluxo de dois passos do Keycloak + cache; 3 e2e contra Keycloak real, incluindo um tools/call de ponta a ponta pelo Gateway usando uma credencial obtida por token exchange, não SALES_DOMAIN_SERVICE_TOKEN) — todos passando. service_account continua o modo padrão dos backends seed (config/backends/*.yaml); token_exchange é uma alternativa disponível e verificada, não o padrão.
8.8 Riscos principais
| # | Risco | Probabilidade | Impacto | Mitigação |
|---|---|---|---|---|
| 1 | Curva de aprendizado de OPA/Rego atrasa EP-03 | Média | Médio | Começar com poucas policies R1 (Fase 1 do roadmap); usar opa test desde o primeiro commit. |
| 2 | Complexidade do realm Keycloak para ambiente local | Baixa | Médio | Usar realm export versionado e reproduzível (EP-01-T01), não configuração manual. |
| 3 | LangGraph aplicado além do necessário, contaminando componentes determinísticos | Média | Baixo | Critério da seção 6 aplicado rigorosamente por task (ver campo justificativa_langgraph_langfuse de cada tarefa). |
| 4 | Cache privado mal configurado causa leakage entre clients (§35.8) | Baixa | Alto | EP-15-T04 testa isolamento de cache explicitamente antes de M4. |
| 5 | Profile Intelligence vira tentativa de "IA de verdade" sem dataset real | Média | Baixo | P7 fixa o escopo como stub baseado em regras; revisão de escopo em EP-09-T02. |
| 6 | Langfuse self-hosted (Postgres+ClickHouse+Redis+S3-compatible) torna o compose pesado | Alta | Médio | Perfil llm-observability opcional e desacoplado do core (M0–M2 não dependem dele). |
| 7 | Divergência entre stack do README (OPA/Keycloak citados) e stack Python definida pelo superprompt | Baixa | Médio | G1/P1 explicitados; confirmar com autor do README assim que possível. |
| 8 | Suíte adversarial (EP-15) subestimada — cobre 18+ cenários | Alta | Médio | Dividida em EP-15-T03/T04 por categoria, incrementável por marco em vez de "big bang" em M4. |
| 9 | Token exchange (RFC 8693) não suportado por todo backend mock | Média | Baixo | Fallback documentado para service account por adapter (P5/ADR-020). |
| 10 | Stakeholders cobrarem federação multi-domínio/multi-cloud antes do previsto | Média | Médio | Roadmap explicita (8.7) que federação e ambientes híbridos são pós-RI (Fase 5 do README), não M0–M4. |
8.9 Resultado do checklist de autovalidação
- ✅ Todo componente do README aparece em pelo menos uma tarefa (ver matriz 8.2); nenhuma tarefa implementa algo ausente do README sem
[PREMISSA]declarada (ver 8.3). - ✅ O YAML do backlog é sintaticamente válido; todos os IDs em
depende_dereferenciam tarefas existentes no próprio backlog (verificação manual cruzada durante a escrita — recomenda-se rodar um linter YAML/schema antes do uso operacional). - ✅ O grafo de dependências é acíclico por construção: dependências apontam sempre para tarefas de épico igual ou numericamente anterior, exceto EP-06→EP-02 (fabric depende do registry, EP-02 < EP-06, sem ciclo) e EP-05-T07→EP-06-T01 (mesmo sentido). M0 (seção 8.7) é executável isoladamente com as 6 tarefas listadas, todas com dependências internas ao próprio conjunto M0.
- ✅ Cada tarefa tem ao menos um entregável e um critério de aceite testável (ver backlog 8.6).
- ✅ Todo uso de LangGraph está restrito a EP-11 (Service Orchestrator — estado, ciclos, checkpointing, HITL) e EP-12-T03 (handoff multiagente); todo uso de Langfuse está restrito a EP-13 (única camada com chamadas reais a LLM). Nenhum roteamento simples (Gateway EP-05, Fabric EP-06, Offering Filter EP-10) usa LangGraph — justificativa explícita em cada tarefa.
- ✅ Nenhuma dependência proprietária entrou sem marca
[NÃO-OSS]; Keycloak e OPA foram verificados como Apache-2.0, Langfuse usado apenas em sua edição self-hosted/OSS (nota em EP-13-T02). - ✅ Todo arquivo YAML de configuração previsto (client registration, capability manifest, client profile, backend credentials) tem tarefa correspondente de JSON Schema + modelo Pydantic + validação em CI (EP-00-T02/T03 fornecem o framework genérico; cada schema específico é entregável de sua tarefa de domínio).
- ✅ Há tarefas explícitas para: autenticação/autorização (EP-01), allowlist de tools por client/tenant (EP-04, EP-05-T02), defesa contra prompt injection via conteúdo de tools (EP-15-T03, governança de publicação em EP-02-T02), gestão de segredos (EP-00-T05, EP-07-T02), auditoria (EP-08) e mascaramento de PII (EP-13-T03, EP-09-T03).
- ✅ Há tarefas de testes de contrato para os servidores MCP (EP-15-T01) e testes end-to-end dos fluxos principais do README (EP-15-T02, cobrindo os 10 testes formais do §45).
- ✅ Nenhuma versão de biblioteca ou API foi afirmada sem certeza — todas as menções a versões usam "verificar" (ex.: EP-05-T08, referência à revisão MCP 2026-07-28) e uma tarefa dedicada (EP-16-T04) confirma versões antes de fixá-las em
pyproject.toml.
Documento gerado seguindo o SUPERPROMPT de planejamento da RI. Confirmado pelo autor em 2026-09-11: G1 (RI/ é criado do zero por este backlog, a partir de EP-00-T01), G3 (OPA é decisão técnica definitiva de PDP, por ser OSS), G4 (Keycloak é decisão técnica definitiva de identidade do MCP Client, por ser OSS), G2/G8 (Python/LangGraph/Langfuse seguem o padrão operacional do "Agent Platform OCI" de Christiano Hoshikawa — ver docs/research/hoshikawa-agent-platform-oci.md e ADR-022/ADR-023) e G11 (§6.2 do README corresponde ao papel de uma plataforma multiagente externa e consumidora do bus, não a um componente da RI — ADR-024, "elevação a Enterprise": o E-MCP-BUS é a camada de governança de catálogo/entitlement sob a qual qualquer plataforma de orquestração como o Agent Platform OCI se conecta, com o "MCP Gateway" interno dela substituído pelo nosso PEP/PDP. Ampliado em seguida pelo autor: nosso Service Orchestrator calcula NBA e NBO com modelo de decisão pluggable — modularidade + compatibilidade = escolha — habilitando dois modos de integração: A, governança-somente sob um orquestrador externo, e B, substituição completa da camada de decisão externa pelo nosso próprio Orchestrator; ambos demonstrados em EP-13-T05/T06). Também verificado via busca web em 2026-09-11 (não apenas suposto): a revisão MCP 2026-07-28 é real/GA e o SDK Python mcp v2 já suporta nativamente os recursos usados em EP-05-T03/T08; OPA está em v1.0+ com Rego v1. Restam abertas G5–G7, G9–G10 (ver 8.3), nenhuma delas bloqueando o início de M0/M1.