# How-To — rodando a RI Guia prático orientado a tarefas. Para entender *o que* cada componente faz e *por que*, ver [`AS-BUILT.md`](AS-BUILT.md). Público-alvo: quem vai **rodar** esta RI. ## Pré-requisitos - Python 3.12+ e `pip`/`uv`. - Opcional: binário `opa` no PATH (ou `OPA_BINARY` apontando para ele) — sem ele, os testes que exercitam o PDP real são pulados (`pytest.skip`), não falham. - Opcional: `docker` no PATH — sem ele, `tests/e2e/test_real_keycloak.py` e `tests/e2e/test_security_acceptance.py` são pulados pela mesma razão. - Opcional: credenciais de um LLM OpenAI-compatible (`LLM_BASE_URL`/`LLM_API_KEY`/`LLM_MODEL`, ver `.env.example`) — sem elas, `tests/e2e/test_example_agent.py` e `tests/e2e/test_multi_agent_mode_a.py` são pulados. ## Instalação ```bash make install # cria .venv e instala o pacote em modo editável (dev extras inclusos) ``` ### Instalar o OPA (sem Docker) OPA é um binário Go estático — não precisa de Docker nem de build: ```bash curl -sL -o /tmp/opa "https://openpolicyagent.org/downloads/latest/opa_linux_amd64_static" chmod +x /tmp/opa cp /tmp/opa .venv/bin/opa opa version ``` Versão de referência usada nesta RI: OPA v1.20.2 (Rego v1 é o padrão — `config/policies/*.rego` usa `import rego.v1`). ## Lint, typecheck e validação de schema ```bash make lint # ruff check + ruff format --check make typecheck # mypy --strict make validate-schemas # valida todo YAML de config/ contra seu modelo Pydantic make export-schemas # regenera schemas/*.schema.json a partir dos modelos ``` ## Rodando os testes ```bash make test # == pytest --forked ``` `--forked` (pytest-forked) isola cada função de teste em seu próprio processo — necessário porque vários pares de servidor ASGI em processo, compartilhando um único event loop entre muitos testes sequenciais na mesma sessão pytest, produziam falhas intermitentes de limpeza assíncrona. Com `--forked`, a suíte é 100% determinística em execuções repetidas. O custo é só tempo de execução (cerca de +1min nesta máquina de referência). `make test`/CI usam `--forked` por padrão. Por suíte, o que cada uma prova: - `pytest tests/unit --forked` — comportamento isolado de cada componente (schemas, PDP, entitlement, authn, registry, downstream identity, approval, fabric router, audit, canonical request, rest adapter, bypass detection, gateway cache, shared client lint, profile intelligence, offering filter + invariantes de propriedade, orchestrator, agent runtime, PII redaction, prompt store). - `pytest tests/e2e --forked` — fluxos ponta a ponta contra um Gateway real em processo (`test_walking_skeleton.py`, `test_governed_gateway.py`), Keycloak/Docker real (`test_real_keycloak.py`), LLM real (`test_example_agent.py`, `test_multi_agent_mode_a.py`), política de falha (`test_fail_closed.py`). - `pytest tests/contract --forked` — o contrato input/output de cada tool, conectando direto a cada domain server (nunca pelo Gateway) — o contrato de uma tool é propriedade do domain server, independente de quem está ou não entitled a chamá-la. - `pytest tests/adversarial --forked` — cenários adversariais de prompt/tool injection (`test_prompt_injection.py`, `test_tool_poisoning.py`, `test_payload_identity_spoofing.py`) e de protocolo/identidade (`test_protocol_identity.py`). - `pytest tests/e2e/test_security_acceptance.py -v` — a suíte de aceite de segurança formal, contra o `docker-compose.yml` real (`--profile core --profile identity`, Keycloak incluído). **Não** roda com `--forked`/`make test` — exige Docker e paga o custo de build+Keycloak por execução, por isso é rodada separadamente: ```bash pytest tests/e2e/test_security_acceptance.py -v ``` ## Subindo a stack local ### Sem Docker ```bash source .venv/bin/activate opa run --server --addr 127.0.0.1:8181 config/policies/ & export SALES_DOMAIN_SERVICE_TOKEN=dev-sales-domain-service-token export FINANCE_DOMAIN_SERVICE_TOKEN=dev-finance-domain-service-token python3 -m example_mcp_servers.sales_domain.server & python3 -m example_mcp_servers.finance_domain.server & python3 -m emcp_bus.gateway.server ``` Sem `OIDC_JWKS_URL`/`OIDC_ISSUER` definidos, o Gateway roda em modo "sem IdP configurado": nega tudo por padrão (`tools/list` vazio, `tools/call` sempre `UNAUTHENTICATED`) — ver [`interfaces/mcp-gateway.md`](interfaces/mcp-gateway.md#dois-modos-de-deployment) para os dois modos. ### Com Docker Compose ```bash make up # == docker compose --profile core up --build (sem IdP) make down make logs ``` Perfis disponíveis (`docker-compose.yml`): | Perfil | Serviços | O que adiciona | |---|---|---| | `core` | `opa`, `sales-domain`, `finance-domain`, `gateway` | PEP/PDP + os dois domínios de exemplo — mínimo funcional | | `identity` | + `keycloak` | autenticação OIDC real | | `observability` | + `otel-collector`, `jaeger` | tracing OTLP (UI Jaeger em `http://localhost:16686`) | Com Keycloak real: ```bash cp .env.example .env # descomente as 3 linhas OIDC_* docker compose --profile core --profile identity up --build ``` Todos os perfis simultaneamente: ```bash docker compose --profile core --profile identity --profile observability up -d --build ``` ## Rodando o agente de exemplo e os modos multiagente Requer `LLM_BASE_URL`/`LLM_API_KEY`/`LLM_MODEL` em `.env` (qualquer endpoint OpenAI-compatible; validado contra `meta.llama-3.3-70b-instruct` via OCI Generative AI). ```bash pytest tests/e2e/test_example_agent.py -v # agente único, tool-calling real pytest tests/e2e/test_multi_agent_mode_a.py -v # Modo A (Global Supervisor externo, ADR-024) pytest tests/e2e/test_real_keycloak.py -k mode_b -v # Modo B (Orchestrator como único cérebro) ``` Modo B exige Docker (sobe seu próprio Keycloak real). ## Publicando uma nova capability 1. Escreva o manifesto YAML em `config/capabilities//.yaml`, seguindo o schema `CapabilityManifest` (ver [`interfaces/registry-capability-manifest.md`](interfaces/registry-capability-manifest.md) para o schema completo, e qualquer arquivo em `config/capabilities/*/` como exemplo real — nome canônico `.`, `risk_tier`, `owner`, `entitlements`, `backend.service` apontando para um backend já registrado em `config/backends/`). 2. Se a capability precisar de um backend novo, registre-o em `config/backends/.yaml` (ver [`interfaces/downstream-identity.md`](interfaces/downstream-identity.md)). 3. Associe a capability a um `ClientProfile` existente ou novo em `config/clients/profiles/.yaml`, adicionando o nome canônico a `allowed_tools`. 4. Rode `make validate-schemas` — valida o novo YAML contra seu modelo Pydantic antes de qualquer coisa. 5. Rode `make export-schemas` se o modelo Pydantic em si mudou (não necessário para apenas adicionar um YAML de exemplo a um schema já existente). 6. Reinicie o Gateway (ou a stack Docker) — o Registry é semeado a partir de `config/capabilities/` no boot (`registry/seed.py`); não há hot-reload nesta RI. ## Registrando um novo MCP Client 1. Registre o client no realm Keycloak (ou equivalente OIDC) com grant `client_credentials` habilitado e um mapper de audience para o `OIDC_AUDIENCE` do Gateway (`emcp-gateway` por padrão) — ver `deploy/keycloak/realm-export.json` para os 3 clients de exemplo já configurados dessa forma. 2. Crie `config/clients/.yaml` (`ClientRegistration`: `client_id`, `profile`, `environment`) apontando para um `ClientProfile` existente ou novo em `config/clients/profiles/`. 3. Rode `make validate-schemas`. 4. Reinicie o Gateway — registros de client também são carregados no boot (`load_yaml_models(config.clients_dir, ClientRegistration)`). ## Observabilidade ### Jaeger (tracing) ```bash docker compose --profile core --profile observability up -d --build # UI: http://localhost:16686 ``` ### Langfuse self-hosted (observabilidade de LLM) ```bash docker compose -f docker-compose.yml -f deploy/langfuse/docker-compose.override.yml \ --profile llm-observability up -d # Aguardar: curl http://localhost:3000/api/public/health # Criar um projeto/API key na UI (http://localhost:3000) ou pré-semear via # LANGFUSE_INIT_PROJECT_PUBLIC_KEY/LANGFUSE_INIT_PROJECT_SECRET_KEY # (ver deploy/langfuse/docker-compose.override.yml). ``` Depois, aponte as variáveis padrão do OTel SDK para o endpoint OTLP nativo do Langfuse (nenhuma mudança de código): ```bash export OTEL_EXPORTER_OTLP_ENDPOINT=http://langfuse-web:3000/api/public/otel export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic " ``` Este perfil é pesado (6 containers: web/worker/postgres/clickhouse/redis/minio) e opt-in, desacoplado dos perfis `core`/`identity`/`observability`.