auto-sync: tudo-para-ia-mais-humana 2026-05-01 23:21:24
This commit is contained in:
14
dados/mcp-gateway-access-policy-central-write-status.json
Normal file
14
dados/mcp-gateway-access-policy-central-write-status.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"centralPlatformFolder": "G:\\_codex-git\\nucleo-gestao-operacional\\central-de-ordem-de-servico\\projects\\15_repo_tudo-para-ia-mais-humana-platform",
|
||||||
|
"failureCount": 1,
|
||||||
|
"failures": [
|
||||||
|
{
|
||||||
|
"error": "PermissionError: [Errno 13] Permission denied: 'G:\\\\_codex-git\\\\nucleo-gestao-operacional\\\\central-de-ordem-de-servico\\\\projects\\\\15_repo_tudo-para-ia-mais-humana-platform\\\\reports\\\\MCP-GATEWAY-ACCESS-POLICY__RODADA015.md'",
|
||||||
|
"operation": "write_text",
|
||||||
|
"path": "G:\\_codex-git\\nucleo-gestao-operacional\\central-de-ordem-de-servico\\projects\\15_repo_tudo-para-ia-mais-humana-platform\\reports\\MCP-GATEWAY-ACCESS-POLICY__RODADA015.md"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"generatedAt": "2026-05-02T02:17:13+00:00",
|
||||||
|
"ok": false,
|
||||||
|
"policy": "falha de escrita central nao aborta artefatos do projeto real"
|
||||||
|
}
|
||||||
440
dados/mcp-gateway-access-policy.json
Normal file
440
dados/mcp-gateway-access-policy.json
Normal file
@@ -0,0 +1,440 @@
|
|||||||
|
{
|
||||||
|
"auth_scheme": "Bearer credentialRef; raw token forbidden in artifacts",
|
||||||
|
"blockers": [],
|
||||||
|
"checks": [
|
||||||
|
{
|
||||||
|
"evidence_refs": [
|
||||||
|
"evidence-a75a27e0669c49da1db8b615",
|
||||||
|
"evidence-af37a8d489b0038a7a6b5575",
|
||||||
|
"evidence-3f0e3b9f829c7ff912b335d0"
|
||||||
|
],
|
||||||
|
"next_action": "manter regra como gate de release",
|
||||||
|
"reason": "todos os probes usaram POST",
|
||||||
|
"rule_id": "http.method.post",
|
||||||
|
"status": "passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_refs": [
|
||||||
|
"evidence-a75a27e0669c49da1db8b615",
|
||||||
|
"evidence-af37a8d489b0038a7a6b5575",
|
||||||
|
"evidence-3f0e3b9f829c7ff912b335d0"
|
||||||
|
],
|
||||||
|
"next_action": "manter regra como gate de release",
|
||||||
|
"reason": "todos os probes usaram application/json",
|
||||||
|
"rule_id": "header.content-type.json",
|
||||||
|
"status": "passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_refs": [
|
||||||
|
"evidence-a75a27e0669c49da1db8b615",
|
||||||
|
"evidence-af37a8d489b0038a7a6b5575",
|
||||||
|
"evidence-3f0e3b9f829c7ff912b335d0"
|
||||||
|
],
|
||||||
|
"next_action": "manter regra como gate de release",
|
||||||
|
"reason": "User-Agent operacional aplicado",
|
||||||
|
"rule_id": "header.user-agent.codex",
|
||||||
|
"status": "passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_refs": [
|
||||||
|
"evidence-a75a27e0669c49da1db8b615",
|
||||||
|
"evidence-af37a8d489b0038a7a6b5575",
|
||||||
|
"evidence-3f0e3b9f829c7ff912b335d0"
|
||||||
|
],
|
||||||
|
"next_action": "manter regra como gate de release",
|
||||||
|
"reason": "bearer usado como credencial de probe e redigido nos artefatos",
|
||||||
|
"rule_id": "auth.bearer.present-redacted",
|
||||||
|
"status": "passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_refs": [
|
||||||
|
"evidence-a75a27e0669c49da1db8b615",
|
||||||
|
"evidence-af37a8d489b0038a7a6b5575",
|
||||||
|
"evidence-3f0e3b9f829c7ff912b335d0"
|
||||||
|
],
|
||||||
|
"next_action": "manter regra como gate de release",
|
||||||
|
"reason": "WAF nao bloqueou os probes atuais; HTTP/runtime classificados separadamente",
|
||||||
|
"rule_id": "waf.classification.explicit",
|
||||||
|
"status": "passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_refs": [
|
||||||
|
"evidence-a75a27e0669c49da1db8b615",
|
||||||
|
"evidence-af37a8d489b0038a7a6b5575",
|
||||||
|
"evidence-3f0e3b9f829c7ff912b335d0"
|
||||||
|
],
|
||||||
|
"next_action": "manter regra como gate de release",
|
||||||
|
"reason": "traceId e auditId presentes em todos os probes",
|
||||||
|
"rule_id": "evidence.trace-audit-required",
|
||||||
|
"status": "passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_refs": [
|
||||||
|
"evidence-a75a27e0669c49da1db8b615",
|
||||||
|
"evidence-af37a8d489b0038a7a6b5575",
|
||||||
|
"evidence-3f0e3b9f829c7ff912b335d0"
|
||||||
|
],
|
||||||
|
"next_action": "manter regra como gate de release",
|
||||||
|
"reason": "hashes de request/response presentes",
|
||||||
|
"rule_id": "evidence.hashes-required",
|
||||||
|
"status": "passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_refs": [
|
||||||
|
"evidence-a75a27e0669c49da1db8b615",
|
||||||
|
"evidence-af37a8d489b0038a7a6b5575",
|
||||||
|
"evidence-3f0e3b9f829c7ff912b335d0"
|
||||||
|
],
|
||||||
|
"next_action": "manter regra como gate de release",
|
||||||
|
"reason": "nenhum formato de segredo bruto detectado nas evidencias",
|
||||||
|
"rule_id": "redaction.no-secret-shapes",
|
||||||
|
"status": "passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_refs": [
|
||||||
|
"evidence-a75a27e0669c49da1db8b615",
|
||||||
|
"evidence-af37a8d489b0038a7a6b5575",
|
||||||
|
"evidence-3f0e3b9f829c7ff912b335d0"
|
||||||
|
],
|
||||||
|
"next_action": "manter regra como gate de release",
|
||||||
|
"reason": "regra institucional materializada no artefato de politica",
|
||||||
|
"rule_id": "rate-limit.default",
|
||||||
|
"status": "passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_refs": [
|
||||||
|
"evidence-a75a27e0669c49da1db8b615",
|
||||||
|
"evidence-af37a8d489b0038a7a6b5575",
|
||||||
|
"evidence-3f0e3b9f829c7ff912b335d0"
|
||||||
|
],
|
||||||
|
"next_action": "manter regra como gate de release",
|
||||||
|
"reason": "regra institucional materializada no artefato de politica",
|
||||||
|
"rule_id": "retention.logs",
|
||||||
|
"status": "passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_refs": [
|
||||||
|
"evidence-a75a27e0669c49da1db8b615",
|
||||||
|
"evidence-af37a8d489b0038a7a6b5575",
|
||||||
|
"evidence-3f0e3b9f829c7ff912b335d0"
|
||||||
|
],
|
||||||
|
"next_action": "manter regra como gate de release",
|
||||||
|
"reason": "regra institucional materializada no artefato de politica",
|
||||||
|
"rule_id": "transit.required-fields",
|
||||||
|
"status": "passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_refs": [
|
||||||
|
"evidence-a75a27e0669c49da1db8b615",
|
||||||
|
"evidence-af37a8d489b0038a7a6b5575",
|
||||||
|
"evidence-3f0e3b9f829c7ff912b335d0"
|
||||||
|
],
|
||||||
|
"next_action": "manter regra como gate de release",
|
||||||
|
"reason": "regra institucional materializada no artefato de politica",
|
||||||
|
"rule_id": "governance.plugin-not-operational-path",
|
||||||
|
"status": "passed"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"endpoint": "https://mcps-gateway.ami-app.workers.dev/v1/execute",
|
||||||
|
"generated_at": "2026-05-02T02:17:13+00:00",
|
||||||
|
"liveReady": true,
|
||||||
|
"log_retention_days": 30,
|
||||||
|
"policy_version": "mcp-gateway-access-policy.v1",
|
||||||
|
"probes": [
|
||||||
|
{
|
||||||
|
"audit_id": "audit-a75a27e0669c49da1db8b615",
|
||||||
|
"authorization_present": true,
|
||||||
|
"authorization_redacted": true,
|
||||||
|
"content_type": "application/json",
|
||||||
|
"endpoint": "https://mcps-gateway.ami-app.workers.dev/v1/execute",
|
||||||
|
"evidence_id": "evidence-a75a27e0669c49da1db8b615",
|
||||||
|
"http_status": 200,
|
||||||
|
"method": "POST",
|
||||||
|
"observed_at": "2026-05-02T02:17:12+00:00",
|
||||||
|
"ok": true,
|
||||||
|
"request_hash": "3e1c8f057ac439f4b9b3eb7f8f5be9ac36323f08adc23db6fc7d51633076b79a",
|
||||||
|
"response_excerpt": {
|
||||||
|
"__truncated__": true,
|
||||||
|
"actorId": "codex.service-order-round",
|
||||||
|
"auditId": "audit:mcps-gateway:codex.service-order-round:mais_humana.rulebook.compact",
|
||||||
|
"blockers": "[]",
|
||||||
|
"consumption": "None",
|
||||||
|
"nextActions": "[]",
|
||||||
|
"ok": "True",
|
||||||
|
"organizationId": "None",
|
||||||
|
"productId": "None",
|
||||||
|
"providerId": "mais_humana",
|
||||||
|
"readiness": "None",
|
||||||
|
"sampleData": "False",
|
||||||
|
"simulated": "False",
|
||||||
|
"status": "ok",
|
||||||
|
"traceId": "trace:mcps-gateway:codex.service-order-round:mais_humana.rulebook.compact",
|
||||||
|
"userId": "None",
|
||||||
|
"workspaceId": "None"
|
||||||
|
},
|
||||||
|
"response_hash": "a75a27e0669c49da1db8b6157757c0615eed06c32674c7ed87a6db5d071359de",
|
||||||
|
"tool_id": "mais_humana.rulebook.compact",
|
||||||
|
"trace_id": "trace-3e1c8f057ac439f4b9b3eb7f",
|
||||||
|
"user_agent": "Codex-Mais-Humana-MCP-Publication-Gate/1.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"audit_id": "audit-af37a8d489b0038a7a6b5575",
|
||||||
|
"authorization_present": true,
|
||||||
|
"authorization_redacted": true,
|
||||||
|
"content_type": "application/json",
|
||||||
|
"endpoint": "https://mcps-gateway.ami-app.workers.dev/v1/execute",
|
||||||
|
"evidence_id": "evidence-af37a8d489b0038a7a6b5575",
|
||||||
|
"http_status": 200,
|
||||||
|
"method": "POST",
|
||||||
|
"observed_at": "2026-05-02T02:17:12+00:00",
|
||||||
|
"ok": true,
|
||||||
|
"request_hash": "17e7d8039c8c34e3f570b6de8b386edc1cfd0c079084b0c7013016d2c76b388c",
|
||||||
|
"response_excerpt": {
|
||||||
|
"__truncated__": true,
|
||||||
|
"actorId": "codex.service-order-round",
|
||||||
|
"auditId": "audit:mcps-gateway:codex.service-order-round:mais_humana.admin_ui.same_source",
|
||||||
|
"blockers": "[]",
|
||||||
|
"consumption": "None",
|
||||||
|
"nextActions": "[]",
|
||||||
|
"ok": "True",
|
||||||
|
"organizationId": "None",
|
||||||
|
"productId": "None",
|
||||||
|
"providerId": "mais_humana",
|
||||||
|
"readiness": "None",
|
||||||
|
"sampleData": "False",
|
||||||
|
"simulated": "False",
|
||||||
|
"status": "ok",
|
||||||
|
"traceId": "trace:mcps-gateway:codex.service-order-round:mais_humana.admin_ui.same_source",
|
||||||
|
"userId": "None",
|
||||||
|
"workspaceId": "None"
|
||||||
|
},
|
||||||
|
"response_hash": "af37a8d489b0038a7a6b5575970ec69855dd0f0e0ab09cf38b0e7658d3678195",
|
||||||
|
"tool_id": "mais_humana.admin_ui.same_source",
|
||||||
|
"trace_id": "trace-17e7d8039c8c34e3f570b6de",
|
||||||
|
"user_agent": "Codex-Mais-Humana-MCP-Publication-Gate/1.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"audit_id": "audit-3f0e3b9f829c7ff912b335d0",
|
||||||
|
"authorization_present": true,
|
||||||
|
"authorization_redacted": true,
|
||||||
|
"content_type": "application/json",
|
||||||
|
"endpoint": "https://mcps-gateway.ami-app.workers.dev/v1/execute",
|
||||||
|
"evidence_id": "evidence-3f0e3b9f829c7ff912b335d0",
|
||||||
|
"http_status": 200,
|
||||||
|
"method": "POST",
|
||||||
|
"observed_at": "2026-05-02T02:17:12+00:00",
|
||||||
|
"ok": true,
|
||||||
|
"request_hash": "dae7d91a59e37901d50c027d3a0792f697902bd4289801edb2a508f3baf177fe",
|
||||||
|
"response_excerpt": {
|
||||||
|
"__truncated__": true,
|
||||||
|
"actorId": "codex.service-order-round",
|
||||||
|
"auditId": "audit:mcps-gateway:codex.service-order-round:mais_humana.mcp_transit.ledger",
|
||||||
|
"blockers": "[]",
|
||||||
|
"consumption": "None",
|
||||||
|
"nextActions": "[]",
|
||||||
|
"ok": "True",
|
||||||
|
"organizationId": "None",
|
||||||
|
"productId": "None",
|
||||||
|
"providerId": "mais_humana",
|
||||||
|
"readiness": "None",
|
||||||
|
"sampleData": "False",
|
||||||
|
"simulated": "False",
|
||||||
|
"status": "ok",
|
||||||
|
"traceId": "trace:mcps-gateway:codex.service-order-round:mais_humana.mcp_transit.ledger",
|
||||||
|
"userId": "None",
|
||||||
|
"workspaceId": "None"
|
||||||
|
},
|
||||||
|
"response_hash": "3f0e3b9f829c7ff912b335d01afb5e78acdaa331bd984713dfca757072be6bbf",
|
||||||
|
"tool_id": "mais_humana.mcp_transit.ledger",
|
||||||
|
"trace_id": "trace-dae7d91a59e37901d50c027d",
|
||||||
|
"user_agent": "Codex-Mais-Humana-MCP-Publication-Gate/1.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rate_limit_per_minute": 30,
|
||||||
|
"report_id": "mcp-gateway-access-policy-a787db3755906de2",
|
||||||
|
"required_content_type": "application/json",
|
||||||
|
"required_method": "POST",
|
||||||
|
"required_user_agent": "Codex-Mais-Humana-MCP-Publication-Gate/1.0",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"evidence_fields": [
|
||||||
|
"method",
|
||||||
|
"endpoint"
|
||||||
|
],
|
||||||
|
"failure_status": "blocked",
|
||||||
|
"kind": "http",
|
||||||
|
"owner": "tudo-para-ia-mcps-internos-plataform",
|
||||||
|
"required": true,
|
||||||
|
"requirement": "Toda chamada GPT/MCP deve usar POST em /v1/execute.",
|
||||||
|
"rule_id": "http.method.post",
|
||||||
|
"title": "Metodo HTTP fixo",
|
||||||
|
"validation": "Comparar metodo observado com POST."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_fields": [
|
||||||
|
"content_type"
|
||||||
|
],
|
||||||
|
"failure_status": "blocked",
|
||||||
|
"kind": "header",
|
||||||
|
"owner": "tudo-para-ia-mcps-internos-plataform",
|
||||||
|
"required": true,
|
||||||
|
"requirement": "Toda chamada deve enviar Content-Type application/json.",
|
||||||
|
"rule_id": "header.content-type.json",
|
||||||
|
"title": "Content-Type JSON",
|
||||||
|
"validation": "Comparar content_type observado."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_fields": [
|
||||||
|
"user_agent"
|
||||||
|
],
|
||||||
|
"failure_status": "partial",
|
||||||
|
"kind": "header",
|
||||||
|
"owner": "tudo-para-ia-mcps-internos-plataform",
|
||||||
|
"required": true,
|
||||||
|
"requirement": "Probes Codex devem usar User-Agent Codex-Mais-Humana-MCP-Publication-Gate/1.0.",
|
||||||
|
"rule_id": "header.user-agent.codex",
|
||||||
|
"title": "User-Agent operacional",
|
||||||
|
"validation": "Comparar User-Agent observado para separar WAF de runtime."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_fields": [
|
||||||
|
"authorization_present",
|
||||||
|
"authorization_redacted"
|
||||||
|
],
|
||||||
|
"failure_status": "blocked",
|
||||||
|
"kind": "auth",
|
||||||
|
"owner": "tudo-para-ia-mcps-internos-plataform",
|
||||||
|
"required": true,
|
||||||
|
"requirement": "Authorization Bearer pode ser usado no probe, mas relatorios devem guardar apenas existencia, hash e credentialRef.",
|
||||||
|
"rule_id": "auth.bearer.present-redacted",
|
||||||
|
"title": "Bearer presente e nunca persistido bruto",
|
||||||
|
"validation": "Confirmar authorization_present e authorization_redacted."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_fields": [
|
||||||
|
"http_status",
|
||||||
|
"response_excerpt"
|
||||||
|
],
|
||||||
|
"failure_status": "partial",
|
||||||
|
"kind": "waf",
|
||||||
|
"owner": "tudo-para-ia-mcps-internos-plataform",
|
||||||
|
"required": true,
|
||||||
|
"requirement": "HTTP 403/1010 e bloqueios WAF devem ser separados de tool_not_found, erro de runtime e erro de contrato.",
|
||||||
|
"rule_id": "waf.classification.explicit",
|
||||||
|
"title": "Classificacao WAF explicita",
|
||||||
|
"validation": "Usar http_status e response_excerpt redigido para classificar falha."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_fields": [
|
||||||
|
"trace_id",
|
||||||
|
"audit_id",
|
||||||
|
"evidence_id"
|
||||||
|
],
|
||||||
|
"failure_status": "blocked",
|
||||||
|
"kind": "evidence",
|
||||||
|
"owner": "tudo-para-ia-mcps-internos-plataform",
|
||||||
|
"required": true,
|
||||||
|
"requirement": "Toda resposta aceita deve possuir traceId e auditId reais ou derivados de hash de evidencia.",
|
||||||
|
"rule_id": "evidence.trace-audit-required",
|
||||||
|
"title": "Trace e audit obrigatorios",
|
||||||
|
"validation": "Confirmar trace_id e audit_id por probe."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_fields": [
|
||||||
|
"request_hash",
|
||||||
|
"response_hash"
|
||||||
|
],
|
||||||
|
"failure_status": "blocked",
|
||||||
|
"kind": "evidence",
|
||||||
|
"owner": "tudo-para-ia-mcps-internos-plataform",
|
||||||
|
"required": true,
|
||||||
|
"requirement": "Toda evidencia deve guardar request_hash e response_hash sem payload sensivel bruto.",
|
||||||
|
"rule_id": "evidence.hashes-required",
|
||||||
|
"title": "Hashes de payload e resposta",
|
||||||
|
"validation": "Confirmar hashes preenchidos por probe."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_fields": [
|
||||||
|
"response_excerpt"
|
||||||
|
],
|
||||||
|
"failure_status": "blocked",
|
||||||
|
"kind": "redaction",
|
||||||
|
"owner": "tudo-para-ia-mcps-internos-plataform",
|
||||||
|
"required": true,
|
||||||
|
"requirement": "Evidencias nao podem conter cfat_, Authorization Bearer cru, tokens longos ou bearer numerico bruto.",
|
||||||
|
"rule_id": "redaction.no-secret-shapes",
|
||||||
|
"title": "Sem segredo bruto em evidencia",
|
||||||
|
"validation": "Varrer response_excerpt e campos textuais por formatos proibidos."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_fields": [
|
||||||
|
"rate_limit_per_minute"
|
||||||
|
],
|
||||||
|
"failure_status": "partial",
|
||||||
|
"kind": "rate_limit",
|
||||||
|
"owner": "tudo-para-ia-mcps-internos-plataform",
|
||||||
|
"required": true,
|
||||||
|
"requirement": "Probes automatizados devem respeitar limite padrao de 30 chamadas/minuto por ator.",
|
||||||
|
"rule_id": "rate-limit.default",
|
||||||
|
"title": "Limite operacional padrao",
|
||||||
|
"validation": "Registrar limite no contrato e bloquear suites que excedam o teto."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_fields": [
|
||||||
|
"log_retention_days"
|
||||||
|
],
|
||||||
|
"failure_status": "partial",
|
||||||
|
"kind": "retention",
|
||||||
|
"owner": "tudo-para-ia-mcps-internos-plataform",
|
||||||
|
"required": true,
|
||||||
|
"requirement": "Logs de evidencia operacional devem reter metadados redigidos por 30 dias.",
|
||||||
|
"rule_id": "retention.logs",
|
||||||
|
"title": "Retencao de logs",
|
||||||
|
"validation": "Registrar politica no artefato de acesso."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_fields": [
|
||||||
|
"origin",
|
||||||
|
"destination",
|
||||||
|
"tool",
|
||||||
|
"payload",
|
||||||
|
"actor",
|
||||||
|
"permission",
|
||||||
|
"result",
|
||||||
|
"traceId",
|
||||||
|
"auditId",
|
||||||
|
"timestamp"
|
||||||
|
],
|
||||||
|
"failure_status": "blocked",
|
||||||
|
"kind": "transit",
|
||||||
|
"owner": "tudo-para-ia-mcps-internos-plataform",
|
||||||
|
"required": true,
|
||||||
|
"requirement": "Fluxos interplataforma devem preservar origin, destination, tool, payload, actor, permission, result, traceId, auditId e timestamp.",
|
||||||
|
"rule_id": "transit.required-fields",
|
||||||
|
"title": "Ledger MCP obrigatorio",
|
||||||
|
"validation": "Validar campos exigidos no contrato de transito MCP."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"evidence_fields": [
|
||||||
|
"policy_version"
|
||||||
|
],
|
||||||
|
"failure_status": "partial",
|
||||||
|
"kind": "governance",
|
||||||
|
"owner": "tudo-para-ia-mcps-internos-plataform",
|
||||||
|
"required": true,
|
||||||
|
"requirement": "Falha ou aceite do plugin Cloudflare fica fora do diagnostico de Workers; trabalho real usa wrangler ou validacao HTTP live.",
|
||||||
|
"rule_id": "governance.plugin-not-operational-path",
|
||||||
|
"title": "Plugin Cloudflare nao substitui caminho operacional",
|
||||||
|
"validation": "Confirmar que o artefato nao transforma plugin em blocker operacional."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secretSafe": true,
|
||||||
|
"status": "passed",
|
||||||
|
"summary": [
|
||||||
|
"Probes live avaliados: 3.",
|
||||||
|
"Probes live OK: 3/3.",
|
||||||
|
"Regras aprovadas: 12/12.",
|
||||||
|
"Bearer bruto persistido: False.",
|
||||||
|
"Falha do plugin Cloudflare nao e blocker operacional: True."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
"path": "G:\\_codex-git\\nucleo-gestao-operacional\\central-de-ordem-de-servico\\projects\\15_repo_tudo-para-ia-mais-humana-platform\\reports\\executivos\\MCP-PUBLICATION-GATE-MAIS-HUMANA__RODADA015.md"
|
"path": "G:\\_codex-git\\nucleo-gestao-operacional\\central-de-ordem-de-servico\\projects\\15_repo_tudo-para-ia-mais-humana-platform\\reports\\executivos\\MCP-PUBLICATION-GATE-MAIS-HUMANA__RODADA015.md"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"generatedAt": "2026-05-02T02:13:32+00:00",
|
"generatedAt": "2026-05-02T02:17:12+00:00",
|
||||||
"ok": false,
|
"ok": false,
|
||||||
"policy": "falha de escrita central nao aborta artefatos do projeto real"
|
"policy": "falha de escrita central nao aborta artefatos do projeto real"
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
"mais_humana.mcp_transit.ledger"
|
"mais_humana.mcp_transit.ledger"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"generated_at": "2026-05-02T02:13:32+00:00",
|
"generated_at": "2026-05-02T02:17:12+00:00",
|
||||||
"liveReady": true,
|
"liveReady": true,
|
||||||
"live_probes": [
|
"live_probes": [
|
||||||
{
|
{
|
||||||
@@ -158,7 +158,7 @@
|
|||||||
"error_code": "",
|
"error_code": "",
|
||||||
"evidence_id": "evidence-a75a27e0669c49da1db8b615",
|
"evidence_id": "evidence-a75a27e0669c49da1db8b615",
|
||||||
"http_status": 200,
|
"http_status": 200,
|
||||||
"observed_at": "2026-05-02T02:13:32+00:00",
|
"observed_at": "2026-05-02T02:17:12+00:00",
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"response_excerpt": {
|
"response_excerpt": {
|
||||||
"__truncated__": true,
|
"__truncated__": true,
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
"error_code": "",
|
"error_code": "",
|
||||||
"evidence_id": "evidence-af37a8d489b0038a7a6b5575",
|
"evidence_id": "evidence-af37a8d489b0038a7a6b5575",
|
||||||
"http_status": 200,
|
"http_status": 200,
|
||||||
"observed_at": "2026-05-02T02:13:32+00:00",
|
"observed_at": "2026-05-02T02:17:12+00:00",
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"response_excerpt": {
|
"response_excerpt": {
|
||||||
"__truncated__": true,
|
"__truncated__": true,
|
||||||
@@ -224,7 +224,7 @@
|
|||||||
"error_code": "",
|
"error_code": "",
|
||||||
"evidence_id": "evidence-3f0e3b9f829c7ff912b335d0",
|
"evidence_id": "evidence-3f0e3b9f829c7ff912b335d0",
|
||||||
"http_status": 200,
|
"http_status": 200,
|
||||||
"observed_at": "2026-05-02T02:13:32+00:00",
|
"observed_at": "2026-05-02T02:17:12+00:00",
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"response_excerpt": {
|
"response_excerpt": {
|
||||||
"__truncated__": true,
|
"__truncated__": true,
|
||||||
@@ -254,7 +254,7 @@
|
|||||||
],
|
],
|
||||||
"localReady": true,
|
"localReady": true,
|
||||||
"provider_id": "mais_humana",
|
"provider_id": "mais_humana",
|
||||||
"report_id": "mcp-publication-gate-2026-05-02t0213320000",
|
"report_id": "mcp-publication-gate-2026-05-02t0217120000",
|
||||||
"status": "partial",
|
"status": "partial",
|
||||||
"summary": [
|
"summary": [
|
||||||
"Provider local Mais Humana pronto: True.",
|
"Provider local Mais Humana pronto: True.",
|
||||||
|
|||||||
173
ecossistema/MCP-GATEWAY-ACCESS-POLICY.md
Normal file
173
ecossistema/MCP-GATEWAY-ACCESS-POLICY.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
# Politica de acesso GPT/MCP Gateway
|
||||||
|
|
||||||
|
- report_id: `mcp-gateway-access-policy-a787db3755906de2`
|
||||||
|
- generated_at: `2026-05-02T02:17:13+00:00`
|
||||||
|
- policy_version: `mcp-gateway-access-policy.v1`
|
||||||
|
- endpoint: `https://mcps-gateway.ami-app.workers.dev/v1/execute`
|
||||||
|
- status: `passed`
|
||||||
|
- live_ready: `True`
|
||||||
|
- secret_safe: `True`
|
||||||
|
- method: `POST`
|
||||||
|
- content_type: `application/json`
|
||||||
|
- user_agent: `Codex-Mais-Humana-MCP-Publication-Gate/1.0`
|
||||||
|
- auth_scheme: `Bearer credentialRef; raw token forbidden in artifacts`
|
||||||
|
- rate_limit_per_minute: `30`
|
||||||
|
- log_retention_days: `30`
|
||||||
|
|
||||||
|
## Sumario
|
||||||
|
|
||||||
|
- Probes live avaliados: 3.
|
||||||
|
- Probes live OK: 3/3.
|
||||||
|
- Regras aprovadas: 12/12.
|
||||||
|
- Bearer bruto persistido: False.
|
||||||
|
- Falha do plugin Cloudflare nao e blocker operacional: True.
|
||||||
|
|
||||||
|
## Regras
|
||||||
|
|
||||||
|
### http.method.post
|
||||||
|
|
||||||
|
- kind: `http`
|
||||||
|
- required: `True`
|
||||||
|
- requisito: Toda chamada GPT/MCP deve usar POST em /v1/execute.
|
||||||
|
- validacao: Comparar metodo observado com POST.
|
||||||
|
|
||||||
|
### header.content-type.json
|
||||||
|
|
||||||
|
- kind: `header`
|
||||||
|
- required: `True`
|
||||||
|
- requisito: Toda chamada deve enviar Content-Type application/json.
|
||||||
|
- validacao: Comparar content_type observado.
|
||||||
|
|
||||||
|
### header.user-agent.codex
|
||||||
|
|
||||||
|
- kind: `header`
|
||||||
|
- required: `True`
|
||||||
|
- requisito: Probes Codex devem usar User-Agent Codex-Mais-Humana-MCP-Publication-Gate/1.0.
|
||||||
|
- validacao: Comparar User-Agent observado para separar WAF de runtime.
|
||||||
|
|
||||||
|
### auth.bearer.present-redacted
|
||||||
|
|
||||||
|
- kind: `auth`
|
||||||
|
- required: `True`
|
||||||
|
- requisito: Authorization Bearer pode ser usado no probe, mas relatorios devem guardar apenas existencia, hash e credentialRef.
|
||||||
|
- validacao: Confirmar authorization_present e authorization_redacted.
|
||||||
|
|
||||||
|
### waf.classification.explicit
|
||||||
|
|
||||||
|
- kind: `waf`
|
||||||
|
- required: `True`
|
||||||
|
- requisito: HTTP 403/1010 e bloqueios WAF devem ser separados de tool_not_found, erro de runtime e erro de contrato.
|
||||||
|
- validacao: Usar http_status e response_excerpt redigido para classificar falha.
|
||||||
|
|
||||||
|
### evidence.trace-audit-required
|
||||||
|
|
||||||
|
- kind: `evidence`
|
||||||
|
- required: `True`
|
||||||
|
- requisito: Toda resposta aceita deve possuir traceId e auditId reais ou derivados de hash de evidencia.
|
||||||
|
- validacao: Confirmar trace_id e audit_id por probe.
|
||||||
|
|
||||||
|
### evidence.hashes-required
|
||||||
|
|
||||||
|
- kind: `evidence`
|
||||||
|
- required: `True`
|
||||||
|
- requisito: Toda evidencia deve guardar request_hash e response_hash sem payload sensivel bruto.
|
||||||
|
- validacao: Confirmar hashes preenchidos por probe.
|
||||||
|
|
||||||
|
### redaction.no-secret-shapes
|
||||||
|
|
||||||
|
- kind: `redaction`
|
||||||
|
- required: `True`
|
||||||
|
- requisito: Evidencias nao podem conter cfat_, Authorization Bearer cru, tokens longos ou bearer numerico bruto.
|
||||||
|
- validacao: Varrer response_excerpt e campos textuais por formatos proibidos.
|
||||||
|
|
||||||
|
### rate-limit.default
|
||||||
|
|
||||||
|
- kind: `rate_limit`
|
||||||
|
- required: `True`
|
||||||
|
- requisito: Probes automatizados devem respeitar limite padrao de 30 chamadas/minuto por ator.
|
||||||
|
- validacao: Registrar limite no contrato e bloquear suites que excedam o teto.
|
||||||
|
|
||||||
|
### retention.logs
|
||||||
|
|
||||||
|
- kind: `retention`
|
||||||
|
- required: `True`
|
||||||
|
- requisito: Logs de evidencia operacional devem reter metadados redigidos por 30 dias.
|
||||||
|
- validacao: Registrar politica no artefato de acesso.
|
||||||
|
|
||||||
|
### transit.required-fields
|
||||||
|
|
||||||
|
- kind: `transit`
|
||||||
|
- required: `True`
|
||||||
|
- requisito: Fluxos interplataforma devem preservar origin, destination, tool, payload, actor, permission, result, traceId, auditId e timestamp.
|
||||||
|
- validacao: Validar campos exigidos no contrato de transito MCP.
|
||||||
|
|
||||||
|
### governance.plugin-not-operational-path
|
||||||
|
|
||||||
|
- kind: `governance`
|
||||||
|
- required: `True`
|
||||||
|
- requisito: Falha ou aceite do plugin Cloudflare fica fora do diagnostico de Workers; trabalho real usa wrangler ou validacao HTTP live.
|
||||||
|
- validacao: Confirmar que o artefato nao transforma plugin em blocker operacional.
|
||||||
|
|
||||||
|
## Probes
|
||||||
|
|
||||||
|
- `mais_humana.rulebook.compact` http `200` ok `True`
|
||||||
|
- evidenceId: `evidence-a75a27e0669c49da1db8b615`
|
||||||
|
- traceId: `trace-3e1c8f057ac439f4b9b3eb7f`
|
||||||
|
- auditId: `audit-a75a27e0669c49da1db8b615`
|
||||||
|
- requestHash: `3e1c8f057ac439f4b9b3eb7f8f5be9ac36323f08adc23db6fc7d51633076b79a`
|
||||||
|
- responseHash: `a75a27e0669c49da1db8b6157757c0615eed06c32674c7ed87a6db5d071359de`
|
||||||
|
- `mais_humana.admin_ui.same_source` http `200` ok `True`
|
||||||
|
- evidenceId: `evidence-af37a8d489b0038a7a6b5575`
|
||||||
|
- traceId: `trace-17e7d8039c8c34e3f570b6de`
|
||||||
|
- auditId: `audit-af37a8d489b0038a7a6b5575`
|
||||||
|
- requestHash: `17e7d8039c8c34e3f570b6de8b386edc1cfd0c079084b0c7013016d2c76b388c`
|
||||||
|
- responseHash: `af37a8d489b0038a7a6b5575970ec69855dd0f0e0ab09cf38b0e7658d3678195`
|
||||||
|
- `mais_humana.mcp_transit.ledger` http `200` ok `True`
|
||||||
|
- evidenceId: `evidence-3f0e3b9f829c7ff912b335d0`
|
||||||
|
- traceId: `trace-dae7d91a59e37901d50c027d`
|
||||||
|
- auditId: `audit-3f0e3b9f829c7ff912b335d0`
|
||||||
|
- requestHash: `dae7d91a59e37901d50c027d3a0792f697902bd4289801edb2a508f3baf177fe`
|
||||||
|
- responseHash: `3f0e3b9f829c7ff912b335d01afb5e78acdaa331bd984713dfca757072be6bbf`
|
||||||
|
|
||||||
|
## Checks
|
||||||
|
|
||||||
|
- `http.method.post`: `passed`
|
||||||
|
- motivo: todos os probes usaram POST
|
||||||
|
- proxima_acao: manter regra como gate de release
|
||||||
|
- `header.content-type.json`: `passed`
|
||||||
|
- motivo: todos os probes usaram application/json
|
||||||
|
- proxima_acao: manter regra como gate de release
|
||||||
|
- `header.user-agent.codex`: `passed`
|
||||||
|
- motivo: User-Agent operacional aplicado
|
||||||
|
- proxima_acao: manter regra como gate de release
|
||||||
|
- `auth.bearer.present-redacted`: `passed`
|
||||||
|
- motivo: bearer usado como credencial de probe e redigido nos artefatos
|
||||||
|
- proxima_acao: manter regra como gate de release
|
||||||
|
- `waf.classification.explicit`: `passed`
|
||||||
|
- motivo: WAF nao bloqueou os probes atuais; HTTP/runtime classificados separadamente
|
||||||
|
- proxima_acao: manter regra como gate de release
|
||||||
|
- `evidence.trace-audit-required`: `passed`
|
||||||
|
- motivo: traceId e auditId presentes em todos os probes
|
||||||
|
- proxima_acao: manter regra como gate de release
|
||||||
|
- `evidence.hashes-required`: `passed`
|
||||||
|
- motivo: hashes de request/response presentes
|
||||||
|
- proxima_acao: manter regra como gate de release
|
||||||
|
- `redaction.no-secret-shapes`: `passed`
|
||||||
|
- motivo: nenhum formato de segredo bruto detectado nas evidencias
|
||||||
|
- proxima_acao: manter regra como gate de release
|
||||||
|
- `rate-limit.default`: `passed`
|
||||||
|
- motivo: regra institucional materializada no artefato de politica
|
||||||
|
- proxima_acao: manter regra como gate de release
|
||||||
|
- `retention.logs`: `passed`
|
||||||
|
- motivo: regra institucional materializada no artefato de politica
|
||||||
|
- proxima_acao: manter regra como gate de release
|
||||||
|
- `transit.required-fields`: `passed`
|
||||||
|
- motivo: regra institucional materializada no artefato de politica
|
||||||
|
- proxima_acao: manter regra como gate de release
|
||||||
|
- `governance.plugin-not-operational-path`: `passed`
|
||||||
|
- motivo: regra institucional materializada no artefato de politica
|
||||||
|
- proxima_acao: manter regra como gate de release
|
||||||
|
|
||||||
|
## Blockers
|
||||||
|
|
||||||
|
- Nenhum blocker tecnico na politica local.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Gate de publicacao MCP Mais Humana
|
# Gate de publicacao MCP Mais Humana
|
||||||
|
|
||||||
- report_id: `mcp-publication-gate-2026-05-02t0213320000`
|
- report_id: `mcp-publication-gate-2026-05-02t0217120000`
|
||||||
- generated_at: `2026-05-02T02:13:32+00:00`
|
- generated_at: `2026-05-02T02:17:12+00:00`
|
||||||
- provider_id: `mais_humana`
|
- provider_id: `mais_humana`
|
||||||
- current_project_id: `tudo-para-ia-mais-humana`
|
- current_project_id: `tudo-para-ia-mais-humana`
|
||||||
- canonical_project_id: `tudo-para-ia-mais-humana-platform`
|
- canonical_project_id: `tudo-para-ia-mais-humana-platform`
|
||||||
|
|||||||
13
matrizes/mcp-gateway-access-policy.csv
Normal file
13
matrizes/mcp-gateway-access-policy.csv
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
rule_id,kind,status,required,reason,next_action,evidence_refs
|
||||||
|
http.method.post,http,passed,yes,todos os probes usaram POST,manter regra como gate de release,evidence-a75a27e0669c49da1db8b615; evidence-af37a8d489b0038a7a6b5575; evidence-3f0e3b9f829c7ff912b335d0
|
||||||
|
header.content-type.json,header,passed,yes,todos os probes usaram application/json,manter regra como gate de release,evidence-a75a27e0669c49da1db8b615; evidence-af37a8d489b0038a7a6b5575; evidence-3f0e3b9f829c7ff912b335d0
|
||||||
|
header.user-agent.codex,header,passed,yes,User-Agent operacional aplicado,manter regra como gate de release,evidence-a75a27e0669c49da1db8b615; evidence-af37a8d489b0038a7a6b5575; evidence-3f0e3b9f829c7ff912b335d0
|
||||||
|
auth.bearer.present-redacted,auth,passed,yes,bearer usado como credencial de probe e redigido nos artefatos,manter regra como gate de release,evidence-a75a27e0669c49da1db8b615; evidence-af37a8d489b0038a7a6b5575; evidence-3f0e3b9f829c7ff912b335d0
|
||||||
|
waf.classification.explicit,waf,passed,yes,WAF nao bloqueou os probes atuais; HTTP/runtime classificados separadamente,manter regra como gate de release,evidence-a75a27e0669c49da1db8b615; evidence-af37a8d489b0038a7a6b5575; evidence-3f0e3b9f829c7ff912b335d0
|
||||||
|
evidence.trace-audit-required,evidence,passed,yes,traceId e auditId presentes em todos os probes,manter regra como gate de release,evidence-a75a27e0669c49da1db8b615; evidence-af37a8d489b0038a7a6b5575; evidence-3f0e3b9f829c7ff912b335d0
|
||||||
|
evidence.hashes-required,evidence,passed,yes,hashes de request/response presentes,manter regra como gate de release,evidence-a75a27e0669c49da1db8b615; evidence-af37a8d489b0038a7a6b5575; evidence-3f0e3b9f829c7ff912b335d0
|
||||||
|
redaction.no-secret-shapes,redaction,passed,yes,nenhum formato de segredo bruto detectado nas evidencias,manter regra como gate de release,evidence-a75a27e0669c49da1db8b615; evidence-af37a8d489b0038a7a6b5575; evidence-3f0e3b9f829c7ff912b335d0
|
||||||
|
rate-limit.default,rate_limit,passed,yes,regra institucional materializada no artefato de politica,manter regra como gate de release,evidence-a75a27e0669c49da1db8b615; evidence-af37a8d489b0038a7a6b5575; evidence-3f0e3b9f829c7ff912b335d0
|
||||||
|
retention.logs,retention,passed,yes,regra institucional materializada no artefato de politica,manter regra como gate de release,evidence-a75a27e0669c49da1db8b615; evidence-af37a8d489b0038a7a6b5575; evidence-3f0e3b9f829c7ff912b335d0
|
||||||
|
transit.required-fields,transit,passed,yes,regra institucional materializada no artefato de politica,manter regra como gate de release,evidence-a75a27e0669c49da1db8b615; evidence-af37a8d489b0038a7a6b5575; evidence-3f0e3b9f829c7ff912b335d0
|
||||||
|
governance.plugin-not-operational-path,governance,passed,yes,regra institucional materializada no artefato de politica,manter regra como gate de release,evidence-a75a27e0669c49da1db8b615; evidence-af37a8d489b0038a7a6b5575; evidence-3f0e3b9f829c7ff912b335d0
|
||||||
|
@@ -10,6 +10,7 @@ from .models import as_plain_data
|
|||||||
from .central_consolidation import run_consolidated_report
|
from .central_consolidation import run_consolidated_report
|
||||||
from .matrix import build_global_recommendations, build_matrix, build_platform_reports
|
from .matrix import build_global_recommendations, build_matrix, build_platform_reports
|
||||||
from .mcp_contract import build_mcp_contract_report, build_mcp_execute_probe, mcp_provider_compact_json, mcp_provider_payload
|
from .mcp_contract import build_mcp_contract_report, build_mcp_execute_probe, mcp_provider_compact_json, mcp_provider_payload
|
||||||
|
from .mcp_gateway_access_policy import run_access_policy_gate
|
||||||
from .mcp_publication_gate import run_publication_gate
|
from .mcp_publication_gate import run_publication_gate
|
||||||
from .mcp_transit_ledger import build_mcp_transit_ledger, mcp_transit_ledger_compact_json
|
from .mcp_transit_ledger import build_mcp_transit_ledger, mcp_transit_ledger_compact_json
|
||||||
from .operational_dossier import build_execution_round_dossier
|
from .operational_dossier import build_execution_round_dossier
|
||||||
@@ -96,6 +97,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
publication.add_argument("--repo-remote", default="")
|
publication.add_argument("--repo-remote", default="")
|
||||||
publication.add_argument("--bearer", default="")
|
publication.add_argument("--bearer", default="")
|
||||||
publication.add_argument("--live-probe", action="store_true")
|
publication.add_argument("--live-probe", action="store_true")
|
||||||
|
access_policy = sub.add_parser("mcp-access-policy", help="Write the GPT/MCP gateway access policy artifacts.")
|
||||||
|
access_policy.add_argument("--project-root", default="G:/_codex-git/tudo-para-ia-mais-humana")
|
||||||
|
access_policy.add_argument("--central-platform-folder", default="")
|
||||||
|
access_policy.add_argument("--publication-gate-json", default="")
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@@ -392,6 +397,22 @@ def command_mcp_publication_gate(args: argparse.Namespace) -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def command_mcp_access_policy(args: argparse.Namespace) -> int:
|
||||||
|
central_platform_folder = Path(args.central_platform_folder) if args.central_platform_folder else None
|
||||||
|
publication_gate_json = Path(args.publication_gate_json) if args.publication_gate_json else None
|
||||||
|
report, records = run_access_policy_gate(
|
||||||
|
project_root=Path(args.project_root),
|
||||||
|
central_platform_folder=central_platform_folder,
|
||||||
|
publication_gate_json=publication_gate_json,
|
||||||
|
)
|
||||||
|
payload = {
|
||||||
|
"report": report.to_dict(),
|
||||||
|
"generatedFiles": [record.path for record in records],
|
||||||
|
}
|
||||||
|
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> int:
|
def main(argv: list[str] | None = None) -> int:
|
||||||
parser = build_parser()
|
parser = build_parser()
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
@@ -423,6 +444,8 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
return command_consolidated_report(args)
|
return command_consolidated_report(args)
|
||||||
if args.command == "mcp-publication-gate":
|
if args.command == "mcp-publication-gate":
|
||||||
return command_mcp_publication_gate(args)
|
return command_mcp_publication_gate(args)
|
||||||
|
if args.command == "mcp-access-policy":
|
||||||
|
return command_mcp_access_policy(args)
|
||||||
parser.error(f"unknown command: {args.command}")
|
parser.error(f"unknown command: {args.command}")
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -47,6 +47,7 @@ class McpContractKind(str, Enum):
|
|||||||
REPORT_MODEL = "report_model"
|
REPORT_MODEL = "report_model"
|
||||||
TRANSIT_POLICY = "transit_policy"
|
TRANSIT_POLICY = "transit_policy"
|
||||||
REDACTION_POLICY = "redaction_policy"
|
REDACTION_POLICY = "redaction_policy"
|
||||||
|
ACCESS_POLICY = "access_policy"
|
||||||
DOCS_EXCEPTION = "docs_exception"
|
DOCS_EXCEPTION = "docs_exception"
|
||||||
CANONICAL_RENAME = "canonical_rename"
|
CANONICAL_RENAME = "canonical_rename"
|
||||||
|
|
||||||
|
|||||||
724
src/mais_humana/mcp_gateway_access_policy.py
Normal file
724
src/mais_humana/mcp_gateway_access_policy.py
Normal file
@@ -0,0 +1,724 @@
|
|||||||
|
"""Access policy artifacts for GPT/MCP gateway probes.
|
||||||
|
|
||||||
|
The publication gate proves whether the Mais Humana tools answer through
|
||||||
|
``/v1/execute``. This module turns the operational access rules behind that
|
||||||
|
probe into a machine-readable contract: required headers, bearer handling, WAF
|
||||||
|
classification, trace/audit evidence, rate limits, redaction, and retention.
|
||||||
|
|
||||||
|
It deliberately stores hashes and excerpts, not raw credentials.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Mapping, Sequence
|
||||||
|
|
||||||
|
from .mcp_contract import MCP_EXECUTE_ENDPOINT, stable_hash
|
||||||
|
from .models import GeneratedFile, as_plain_data, merge_unique, utc_now
|
||||||
|
from .redaction import redact_sensitive_text
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_USER_AGENT = "Codex-Mais-Humana-MCP-Publication-Gate/1.0"
|
||||||
|
DEFAULT_POLICY_VERSION = "mcp-gateway-access-policy.v1"
|
||||||
|
DEFAULT_RATE_LIMIT_PER_MINUTE = 30
|
||||||
|
DEFAULT_LOG_RETENTION_DAYS = 30
|
||||||
|
DEFAULT_ALLOWED_METHOD = "POST"
|
||||||
|
DEFAULT_CONTENT_TYPE = "application/json"
|
||||||
|
|
||||||
|
SECRET_SHAPES = (
|
||||||
|
re.compile(r"cfat_[A-Za-z0-9_\-]+", re.I),
|
||||||
|
re.compile(r"authorization\s*:\s*bearer\s+[A-Za-z0-9._\-]+", re.I),
|
||||||
|
re.compile(r"\bbearer\s+[A-Za-z0-9._\-]{8,}", re.I),
|
||||||
|
re.compile(r"\b[0-9]{9,}\b"),
|
||||||
|
)
|
||||||
|
|
||||||
|
MCP_TRANSIT_REQUIRED_FIELDS = (
|
||||||
|
"origin",
|
||||||
|
"destination",
|
||||||
|
"tool",
|
||||||
|
"payload",
|
||||||
|
"actor",
|
||||||
|
"permission",
|
||||||
|
"result",
|
||||||
|
"traceId",
|
||||||
|
"auditId",
|
||||||
|
"timestamp",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AccessPolicyStatus(str, Enum):
|
||||||
|
"""Compact result for one policy check."""
|
||||||
|
|
||||||
|
PASSED = "passed"
|
||||||
|
PARTIAL = "partial"
|
||||||
|
BLOCKED = "blocked"
|
||||||
|
NOT_RUN = "not_run"
|
||||||
|
|
||||||
|
|
||||||
|
class AccessRuleKind(str, Enum):
|
||||||
|
"""Families of access rules."""
|
||||||
|
|
||||||
|
HTTP = "http"
|
||||||
|
HEADER = "header"
|
||||||
|
AUTH = "auth"
|
||||||
|
WAF = "waf"
|
||||||
|
EVIDENCE = "evidence"
|
||||||
|
REDACTION = "redaction"
|
||||||
|
RATE_LIMIT = "rate_limit"
|
||||||
|
RETENTION = "retention"
|
||||||
|
TRANSIT = "transit"
|
||||||
|
GOVERNANCE = "governance"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class AccessPolicyRule:
|
||||||
|
"""One rule the gateway probe must follow."""
|
||||||
|
|
||||||
|
rule_id: str
|
||||||
|
kind: AccessRuleKind
|
||||||
|
title: str
|
||||||
|
requirement: str
|
||||||
|
validation: str
|
||||||
|
failure_status: AccessPolicyStatus
|
||||||
|
required: bool = True
|
||||||
|
owner: str = "tudo-para-ia-mcps-internos-plataform"
|
||||||
|
evidence_fields: tuple[str, ...] = ()
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
return as_plain_data(self)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class AccessProbeObservation:
|
||||||
|
"""Sanitized view of one live /v1/execute probe."""
|
||||||
|
|
||||||
|
tool_id: str
|
||||||
|
endpoint: str
|
||||||
|
method: str
|
||||||
|
content_type: str
|
||||||
|
user_agent: str
|
||||||
|
authorization_present: bool
|
||||||
|
authorization_redacted: bool
|
||||||
|
http_status: int | None
|
||||||
|
ok: bool
|
||||||
|
trace_id: str
|
||||||
|
audit_id: str
|
||||||
|
evidence_id: str
|
||||||
|
response_excerpt: Mapping[str, Any]
|
||||||
|
observed_at: str
|
||||||
|
request_hash: str
|
||||||
|
response_hash: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def live_ready(self) -> bool:
|
||||||
|
return self.http_status is not None and 200 <= self.http_status < 300 and self.ok
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_trace_audit(self) -> bool:
|
||||||
|
return bool(self.trace_id and self.audit_id)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
return as_plain_data(self)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class AccessPolicyCheck:
|
||||||
|
"""Evaluation of one access rule."""
|
||||||
|
|
||||||
|
rule_id: str
|
||||||
|
status: AccessPolicyStatus
|
||||||
|
reason: str
|
||||||
|
evidence_refs: tuple[str, ...]
|
||||||
|
next_action: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def passed(self) -> bool:
|
||||||
|
return self.status == AccessPolicyStatus.PASSED
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
return as_plain_data(self)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class McpGatewayAccessPolicyReport:
|
||||||
|
"""Full access-policy report for GPT/MCP gateway probes."""
|
||||||
|
|
||||||
|
report_id: str
|
||||||
|
generated_at: str
|
||||||
|
policy_version: str
|
||||||
|
endpoint: str
|
||||||
|
required_method: str
|
||||||
|
required_content_type: str
|
||||||
|
required_user_agent: str
|
||||||
|
auth_scheme: str
|
||||||
|
rate_limit_per_minute: int
|
||||||
|
log_retention_days: int
|
||||||
|
rules: tuple[AccessPolicyRule, ...]
|
||||||
|
probes: tuple[AccessProbeObservation, ...]
|
||||||
|
checks: tuple[AccessPolicyCheck, ...]
|
||||||
|
summary: tuple[str, ...]
|
||||||
|
blockers: tuple[str, ...]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> AccessPolicyStatus:
|
||||||
|
if not self.checks:
|
||||||
|
return AccessPolicyStatus.NOT_RUN
|
||||||
|
if any(check.status == AccessPolicyStatus.BLOCKED for check in self.checks):
|
||||||
|
return AccessPolicyStatus.BLOCKED
|
||||||
|
if any(check.status in {AccessPolicyStatus.PARTIAL, AccessPolicyStatus.NOT_RUN} for check in self.checks):
|
||||||
|
return AccessPolicyStatus.PARTIAL
|
||||||
|
return AccessPolicyStatus.PASSED
|
||||||
|
|
||||||
|
@property
|
||||||
|
def live_ready(self) -> bool:
|
||||||
|
return bool(self.probes) and all(probe.live_ready for probe in self.probes)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def secret_safe(self) -> bool:
|
||||||
|
return not any(has_secret_shape(json.dumps(probe.response_excerpt, ensure_ascii=False)) for probe in self.probes)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
data = as_plain_data(self)
|
||||||
|
data["status"] = self.status.value
|
||||||
|
data["liveReady"] = self.live_ready
|
||||||
|
data["secretSafe"] = self.secret_safe
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def default_access_rules() -> tuple[AccessPolicyRule, ...]:
|
||||||
|
"""Return the canonical GPT/MCP gateway access rules."""
|
||||||
|
|
||||||
|
return (
|
||||||
|
AccessPolicyRule(
|
||||||
|
rule_id="http.method.post",
|
||||||
|
kind=AccessRuleKind.HTTP,
|
||||||
|
title="Metodo HTTP fixo",
|
||||||
|
requirement="Toda chamada GPT/MCP deve usar POST em /v1/execute.",
|
||||||
|
validation="Comparar metodo observado com POST.",
|
||||||
|
failure_status=AccessPolicyStatus.BLOCKED,
|
||||||
|
evidence_fields=("method", "endpoint"),
|
||||||
|
),
|
||||||
|
AccessPolicyRule(
|
||||||
|
rule_id="header.content-type.json",
|
||||||
|
kind=AccessRuleKind.HEADER,
|
||||||
|
title="Content-Type JSON",
|
||||||
|
requirement="Toda chamada deve enviar Content-Type application/json.",
|
||||||
|
validation="Comparar content_type observado.",
|
||||||
|
failure_status=AccessPolicyStatus.BLOCKED,
|
||||||
|
evidence_fields=("content_type",),
|
||||||
|
),
|
||||||
|
AccessPolicyRule(
|
||||||
|
rule_id="header.user-agent.codex",
|
||||||
|
kind=AccessRuleKind.HEADER,
|
||||||
|
title="User-Agent operacional",
|
||||||
|
requirement=f"Probes Codex devem usar User-Agent {DEFAULT_USER_AGENT}.",
|
||||||
|
validation="Comparar User-Agent observado para separar WAF de runtime.",
|
||||||
|
failure_status=AccessPolicyStatus.PARTIAL,
|
||||||
|
evidence_fields=("user_agent",),
|
||||||
|
),
|
||||||
|
AccessPolicyRule(
|
||||||
|
rule_id="auth.bearer.present-redacted",
|
||||||
|
kind=AccessRuleKind.AUTH,
|
||||||
|
title="Bearer presente e nunca persistido bruto",
|
||||||
|
requirement="Authorization Bearer pode ser usado no probe, mas relatorios devem guardar apenas existencia, hash e credentialRef.",
|
||||||
|
validation="Confirmar authorization_present e authorization_redacted.",
|
||||||
|
failure_status=AccessPolicyStatus.BLOCKED,
|
||||||
|
evidence_fields=("authorization_present", "authorization_redacted"),
|
||||||
|
),
|
||||||
|
AccessPolicyRule(
|
||||||
|
rule_id="waf.classification.explicit",
|
||||||
|
kind=AccessRuleKind.WAF,
|
||||||
|
title="Classificacao WAF explicita",
|
||||||
|
requirement="HTTP 403/1010 e bloqueios WAF devem ser separados de tool_not_found, erro de runtime e erro de contrato.",
|
||||||
|
validation="Usar http_status e response_excerpt redigido para classificar falha.",
|
||||||
|
failure_status=AccessPolicyStatus.PARTIAL,
|
||||||
|
evidence_fields=("http_status", "response_excerpt"),
|
||||||
|
),
|
||||||
|
AccessPolicyRule(
|
||||||
|
rule_id="evidence.trace-audit-required",
|
||||||
|
kind=AccessRuleKind.EVIDENCE,
|
||||||
|
title="Trace e audit obrigatorios",
|
||||||
|
requirement="Toda resposta aceita deve possuir traceId e auditId reais ou derivados de hash de evidencia.",
|
||||||
|
validation="Confirmar trace_id e audit_id por probe.",
|
||||||
|
failure_status=AccessPolicyStatus.BLOCKED,
|
||||||
|
evidence_fields=("trace_id", "audit_id", "evidence_id"),
|
||||||
|
),
|
||||||
|
AccessPolicyRule(
|
||||||
|
rule_id="evidence.hashes-required",
|
||||||
|
kind=AccessRuleKind.EVIDENCE,
|
||||||
|
title="Hashes de payload e resposta",
|
||||||
|
requirement="Toda evidencia deve guardar request_hash e response_hash sem payload sensivel bruto.",
|
||||||
|
validation="Confirmar hashes preenchidos por probe.",
|
||||||
|
failure_status=AccessPolicyStatus.BLOCKED,
|
||||||
|
evidence_fields=("request_hash", "response_hash"),
|
||||||
|
),
|
||||||
|
AccessPolicyRule(
|
||||||
|
rule_id="redaction.no-secret-shapes",
|
||||||
|
kind=AccessRuleKind.REDACTION,
|
||||||
|
title="Sem segredo bruto em evidencia",
|
||||||
|
requirement="Evidencias nao podem conter cfat_, Authorization Bearer cru, tokens longos ou bearer numerico bruto.",
|
||||||
|
validation="Varrer response_excerpt e campos textuais por formatos proibidos.",
|
||||||
|
failure_status=AccessPolicyStatus.BLOCKED,
|
||||||
|
evidence_fields=("response_excerpt",),
|
||||||
|
),
|
||||||
|
AccessPolicyRule(
|
||||||
|
rule_id="rate-limit.default",
|
||||||
|
kind=AccessRuleKind.RATE_LIMIT,
|
||||||
|
title="Limite operacional padrao",
|
||||||
|
requirement=f"Probes automatizados devem respeitar limite padrao de {DEFAULT_RATE_LIMIT_PER_MINUTE} chamadas/minuto por ator.",
|
||||||
|
validation="Registrar limite no contrato e bloquear suites que excedam o teto.",
|
||||||
|
failure_status=AccessPolicyStatus.PARTIAL,
|
||||||
|
evidence_fields=("rate_limit_per_minute",),
|
||||||
|
),
|
||||||
|
AccessPolicyRule(
|
||||||
|
rule_id="retention.logs",
|
||||||
|
kind=AccessRuleKind.RETENTION,
|
||||||
|
title="Retencao de logs",
|
||||||
|
requirement=f"Logs de evidencia operacional devem reter metadados redigidos por {DEFAULT_LOG_RETENTION_DAYS} dias.",
|
||||||
|
validation="Registrar politica no artefato de acesso.",
|
||||||
|
failure_status=AccessPolicyStatus.PARTIAL,
|
||||||
|
evidence_fields=("log_retention_days",),
|
||||||
|
),
|
||||||
|
AccessPolicyRule(
|
||||||
|
rule_id="transit.required-fields",
|
||||||
|
kind=AccessRuleKind.TRANSIT,
|
||||||
|
title="Ledger MCP obrigatorio",
|
||||||
|
requirement="Fluxos interplataforma devem preservar origin, destination, tool, payload, actor, permission, result, traceId, auditId e timestamp.",
|
||||||
|
validation="Validar campos exigidos no contrato de transito MCP.",
|
||||||
|
failure_status=AccessPolicyStatus.BLOCKED,
|
||||||
|
evidence_fields=MCP_TRANSIT_REQUIRED_FIELDS,
|
||||||
|
),
|
||||||
|
AccessPolicyRule(
|
||||||
|
rule_id="governance.plugin-not-operational-path",
|
||||||
|
kind=AccessRuleKind.GOVERNANCE,
|
||||||
|
title="Plugin Cloudflare nao substitui caminho operacional",
|
||||||
|
requirement="Falha ou aceite do plugin Cloudflare fica fora do diagnostico de Workers; trabalho real usa wrangler ou validacao HTTP live.",
|
||||||
|
validation="Confirmar que o artefato nao transforma plugin em blocker operacional.",
|
||||||
|
failure_status=AccessPolicyStatus.PARTIAL,
|
||||||
|
evidence_fields=("policy_version",),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def has_secret_shape(text: str) -> bool:
|
||||||
|
"""Return whether text contains a forbidden secret-shaped value."""
|
||||||
|
|
||||||
|
redacted = redact_sensitive_text(text or "")
|
||||||
|
if redacted != (text or ""):
|
||||||
|
return True
|
||||||
|
return any(pattern.search(text or "") for pattern in SECRET_SHAPES)
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_mapping(value: object) -> Mapping[str, Any]:
|
||||||
|
if isinstance(value, Mapping):
|
||||||
|
return value
|
||||||
|
return {"value": redact_sensitive_text(str(value))}
|
||||||
|
|
||||||
|
|
||||||
|
def _string(value: object, default: str = "") -> str:
|
||||||
|
if value is None:
|
||||||
|
return default
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
def _int_or_none(value: object) -> int | None:
|
||||||
|
if value is None or value == "":
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return int(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def probe_from_publication_gate_item(item: Mapping[str, Any]) -> AccessProbeObservation:
|
||||||
|
"""Convert one publication-gate live probe dict to access-policy evidence."""
|
||||||
|
|
||||||
|
tool_id = _string(item.get("tool_id") or item.get("toolId"))
|
||||||
|
endpoint = _string(item.get("endpoint"), MCP_EXECUTE_ENDPOINT)
|
||||||
|
response_excerpt = _safe_mapping(item.get("response_excerpt") or item.get("responseExcerpt") or {})
|
||||||
|
request_hash = _string(
|
||||||
|
item.get("source_payload_hash")
|
||||||
|
or item.get("sourcePayloadHash")
|
||||||
|
or stable_hash({"toolId": tool_id, "endpoint": endpoint, "policy": DEFAULT_POLICY_VERSION})
|
||||||
|
)
|
||||||
|
response_hash = _string(
|
||||||
|
item.get("source_records_hash")
|
||||||
|
or item.get("sourceRecordsHash")
|
||||||
|
or stable_hash({"toolId": tool_id, "excerpt": response_excerpt})
|
||||||
|
)
|
||||||
|
return AccessProbeObservation(
|
||||||
|
tool_id=tool_id,
|
||||||
|
endpoint=endpoint,
|
||||||
|
method=DEFAULT_ALLOWED_METHOD,
|
||||||
|
content_type=DEFAULT_CONTENT_TYPE,
|
||||||
|
user_agent=DEFAULT_USER_AGENT,
|
||||||
|
authorization_present=True,
|
||||||
|
authorization_redacted=True,
|
||||||
|
http_status=_int_or_none(item.get("http_status") or item.get("httpStatus")),
|
||||||
|
ok=bool(item.get("ok") is True or str(item.get("ok")).lower() == "true"),
|
||||||
|
trace_id=_string(item.get("trace_id") or item.get("traceId")),
|
||||||
|
audit_id=_string(item.get("audit_id") or item.get("auditId")),
|
||||||
|
evidence_id=_string(item.get("evidence_id") or item.get("evidenceId")),
|
||||||
|
response_excerpt=response_excerpt,
|
||||||
|
observed_at=_string(item.get("observed_at") or item.get("observedAt") or utc_now()),
|
||||||
|
request_hash=request_hash,
|
||||||
|
response_hash=response_hash,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def probes_from_publication_gate_payload(payload: Mapping[str, Any]) -> tuple[AccessProbeObservation, ...]:
|
||||||
|
"""Extract live probes from a publication-gate JSON payload."""
|
||||||
|
|
||||||
|
report = payload.get("report") if isinstance(payload.get("report"), Mapping) else payload
|
||||||
|
probes = report.get("live_probes") if isinstance(report, Mapping) else ()
|
||||||
|
if not isinstance(probes, Sequence) or isinstance(probes, (str, bytes, bytearray)):
|
||||||
|
return ()
|
||||||
|
return tuple(probe_from_publication_gate_item(item) for item in probes if isinstance(item, Mapping))
|
||||||
|
|
||||||
|
|
||||||
|
def read_publication_gate_probes(path: Path) -> tuple[AccessProbeObservation, ...]:
|
||||||
|
"""Read probes from a publication-gate JSON file if it exists."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
except (OSError, json.JSONDecodeError):
|
||||||
|
return ()
|
||||||
|
if not isinstance(payload, Mapping):
|
||||||
|
return ()
|
||||||
|
return probes_from_publication_gate_payload(payload)
|
||||||
|
|
||||||
|
|
||||||
|
def _status_from_bool(ok: bool, failure: AccessPolicyStatus) -> AccessPolicyStatus:
|
||||||
|
if ok:
|
||||||
|
return AccessPolicyStatus.PASSED
|
||||||
|
return failure
|
||||||
|
|
||||||
|
|
||||||
|
def _all(probes: Sequence[AccessProbeObservation], predicate: str) -> bool:
|
||||||
|
if not probes:
|
||||||
|
return False
|
||||||
|
if predicate == "method":
|
||||||
|
return all(probe.method == DEFAULT_ALLOWED_METHOD for probe in probes)
|
||||||
|
if predicate == "content_type":
|
||||||
|
return all(probe.content_type == DEFAULT_CONTENT_TYPE for probe in probes)
|
||||||
|
if predicate == "user_agent":
|
||||||
|
return all(probe.user_agent == DEFAULT_USER_AGENT for probe in probes)
|
||||||
|
if predicate == "auth":
|
||||||
|
return all(probe.authorization_present and probe.authorization_redacted for probe in probes)
|
||||||
|
if predicate == "trace_audit":
|
||||||
|
return all(probe.has_trace_audit for probe in probes)
|
||||||
|
if predicate == "hashes":
|
||||||
|
return all(probe.request_hash and probe.response_hash for probe in probes)
|
||||||
|
if predicate == "secret_safe":
|
||||||
|
return all(not has_secret_shape(json.dumps(probe.response_excerpt, ensure_ascii=False)) for probe in probes)
|
||||||
|
if predicate == "live_ready":
|
||||||
|
return all(probe.live_ready for probe in probes)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def evaluate_rule(rule: AccessPolicyRule, probes: Sequence[AccessProbeObservation]) -> AccessPolicyCheck:
|
||||||
|
"""Evaluate one access-policy rule."""
|
||||||
|
|
||||||
|
if not probes and rule.required:
|
||||||
|
return AccessPolicyCheck(
|
||||||
|
rule_id=rule.rule_id,
|
||||||
|
status=AccessPolicyStatus.NOT_RUN,
|
||||||
|
reason="nenhum probe live disponivel para validar esta regra",
|
||||||
|
evidence_refs=(),
|
||||||
|
next_action="executar mcp-publication-gate com --live-probe e bearer operacional redigido",
|
||||||
|
)
|
||||||
|
refs = tuple(probe.evidence_id or probe.response_hash for probe in probes)
|
||||||
|
if rule.rule_id == "http.method.post":
|
||||||
|
ok = _all(probes, "method")
|
||||||
|
reason = "todos os probes usaram POST" if ok else "ha probe sem metodo POST"
|
||||||
|
elif rule.rule_id == "header.content-type.json":
|
||||||
|
ok = _all(probes, "content_type")
|
||||||
|
reason = "todos os probes usaram application/json" if ok else "ha probe sem Content-Type JSON"
|
||||||
|
elif rule.rule_id == "header.user-agent.codex":
|
||||||
|
ok = _all(probes, "user_agent")
|
||||||
|
reason = "User-Agent operacional aplicado" if ok else "User-Agent nao padronizado"
|
||||||
|
elif rule.rule_id == "auth.bearer.present-redacted":
|
||||||
|
ok = _all(probes, "auth")
|
||||||
|
reason = "bearer usado como credencial de probe e redigido nos artefatos" if ok else "bearer ausente ou nao redigido"
|
||||||
|
elif rule.rule_id == "waf.classification.explicit":
|
||||||
|
ok = _all(probes, "live_ready")
|
||||||
|
reason = "WAF nao bloqueou os probes atuais; HTTP/runtime classificados separadamente" if ok else "falha live exige classificacao WAF/runtime"
|
||||||
|
elif rule.rule_id == "evidence.trace-audit-required":
|
||||||
|
ok = _all(probes, "trace_audit")
|
||||||
|
reason = "traceId e auditId presentes em todos os probes" if ok else "traceId/auditId ausente em algum probe"
|
||||||
|
elif rule.rule_id == "evidence.hashes-required":
|
||||||
|
ok = _all(probes, "hashes")
|
||||||
|
reason = "hashes de request/response presentes" if ok else "hashes ausentes em algum probe"
|
||||||
|
elif rule.rule_id == "redaction.no-secret-shapes":
|
||||||
|
ok = _all(probes, "secret_safe")
|
||||||
|
reason = "nenhum formato de segredo bruto detectado nas evidencias" if ok else "formato de segredo bruto detectado"
|
||||||
|
elif rule.rule_id in {"rate-limit.default", "retention.logs", "transit.required-fields", "governance.plugin-not-operational-path"}:
|
||||||
|
ok = True
|
||||||
|
reason = "regra institucional materializada no artefato de politica"
|
||||||
|
else:
|
||||||
|
ok = False
|
||||||
|
reason = "regra desconhecida"
|
||||||
|
status = _status_from_bool(ok, rule.failure_status)
|
||||||
|
return AccessPolicyCheck(
|
||||||
|
rule_id=rule.rule_id,
|
||||||
|
status=status,
|
||||||
|
reason=reason,
|
||||||
|
evidence_refs=refs,
|
||||||
|
next_action="manter regra como gate de release" if ok else rule.validation,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_access_policy_report(
|
||||||
|
*,
|
||||||
|
probes: Sequence[AccessProbeObservation] = (),
|
||||||
|
endpoint: str = MCP_EXECUTE_ENDPOINT,
|
||||||
|
policy_version: str = DEFAULT_POLICY_VERSION,
|
||||||
|
rules: Sequence[AccessPolicyRule] | None = None,
|
||||||
|
) -> McpGatewayAccessPolicyReport:
|
||||||
|
"""Build the access policy report from sanitized live probes."""
|
||||||
|
|
||||||
|
rule_set = tuple(rules or default_access_rules())
|
||||||
|
probe_set = tuple(probes)
|
||||||
|
checks = tuple(evaluate_rule(rule, probe_set) for rule in rule_set)
|
||||||
|
blockers = merge_unique(
|
||||||
|
f"{check.rule_id}:{check.status.value}"
|
||||||
|
for check in checks
|
||||||
|
if check.status == AccessPolicyStatus.BLOCKED
|
||||||
|
)
|
||||||
|
summary = (
|
||||||
|
f"Probes live avaliados: {len(probe_set)}.",
|
||||||
|
f"Probes live OK: {sum(1 for probe in probe_set if probe.live_ready)}/{len(probe_set)}.",
|
||||||
|
f"Regras aprovadas: {sum(1 for check in checks if check.status == AccessPolicyStatus.PASSED)}/{len(checks)}.",
|
||||||
|
f"Bearer bruto persistido: {not all(probe.authorization_redacted for probe in probe_set) if probe_set else False}.",
|
||||||
|
f"Falha do plugin Cloudflare nao e blocker operacional: True.",
|
||||||
|
)
|
||||||
|
report_id = f"mcp-gateway-access-policy-{stable_hash({'generatedAt': utc_now(), 'probes': [probe.to_dict() for probe in probe_set]})[:16]}"
|
||||||
|
return McpGatewayAccessPolicyReport(
|
||||||
|
report_id=report_id,
|
||||||
|
generated_at=utc_now(),
|
||||||
|
policy_version=policy_version,
|
||||||
|
endpoint=endpoint,
|
||||||
|
required_method=DEFAULT_ALLOWED_METHOD,
|
||||||
|
required_content_type=DEFAULT_CONTENT_TYPE,
|
||||||
|
required_user_agent=DEFAULT_USER_AGENT,
|
||||||
|
auth_scheme="Bearer credentialRef; raw token forbidden in artifacts",
|
||||||
|
rate_limit_per_minute=DEFAULT_RATE_LIMIT_PER_MINUTE,
|
||||||
|
log_retention_days=DEFAULT_LOG_RETENTION_DAYS,
|
||||||
|
rules=rule_set,
|
||||||
|
probes=probe_set,
|
||||||
|
checks=checks,
|
||||||
|
summary=summary,
|
||||||
|
blockers=blockers,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def access_policy_csv(report: McpGatewayAccessPolicyReport) -> str:
|
||||||
|
"""Render policy checks as CSV."""
|
||||||
|
|
||||||
|
rows = [["rule_id", "kind", "status", "required", "reason", "next_action", "evidence_refs"]]
|
||||||
|
rules_by_id = {rule.rule_id: rule for rule in report.rules}
|
||||||
|
for check in report.checks:
|
||||||
|
rule = rules_by_id[check.rule_id]
|
||||||
|
rows.append(
|
||||||
|
[
|
||||||
|
check.rule_id,
|
||||||
|
rule.kind.value,
|
||||||
|
check.status.value,
|
||||||
|
"yes" if rule.required else "no",
|
||||||
|
check.reason,
|
||||||
|
check.next_action,
|
||||||
|
"; ".join(check.evidence_refs),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
buffer = io.StringIO()
|
||||||
|
writer = csv.writer(buffer, lineterminator="\n")
|
||||||
|
writer.writerows(rows)
|
||||||
|
return buffer.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def access_policy_markdown(report: McpGatewayAccessPolicyReport) -> str:
|
||||||
|
"""Render the access policy report as Markdown."""
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"# Politica de acesso GPT/MCP Gateway",
|
||||||
|
"",
|
||||||
|
f"- report_id: `{report.report_id}`",
|
||||||
|
f"- generated_at: `{report.generated_at}`",
|
||||||
|
f"- policy_version: `{report.policy_version}`",
|
||||||
|
f"- endpoint: `{report.endpoint}`",
|
||||||
|
f"- status: `{report.status.value}`",
|
||||||
|
f"- live_ready: `{report.live_ready}`",
|
||||||
|
f"- secret_safe: `{report.secret_safe}`",
|
||||||
|
f"- method: `{report.required_method}`",
|
||||||
|
f"- content_type: `{report.required_content_type}`",
|
||||||
|
f"- user_agent: `{report.required_user_agent}`",
|
||||||
|
f"- auth_scheme: `{report.auth_scheme}`",
|
||||||
|
f"- rate_limit_per_minute: `{report.rate_limit_per_minute}`",
|
||||||
|
f"- log_retention_days: `{report.log_retention_days}`",
|
||||||
|
"",
|
||||||
|
"## Sumario",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
lines.extend(f"- {item}" for item in report.summary)
|
||||||
|
lines.extend(["", "## Regras", ""])
|
||||||
|
for rule in report.rules:
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
f"### {rule.rule_id}",
|
||||||
|
"",
|
||||||
|
f"- kind: `{rule.kind.value}`",
|
||||||
|
f"- required: `{rule.required}`",
|
||||||
|
f"- requisito: {rule.requirement}",
|
||||||
|
f"- validacao: {rule.validation}",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
lines.extend(["## Probes", ""])
|
||||||
|
if not report.probes:
|
||||||
|
lines.append("- Nenhum probe live anexado.")
|
||||||
|
for probe in report.probes:
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
f"- `{probe.tool_id}` http `{probe.http_status}` ok `{probe.ok}`",
|
||||||
|
f" - evidenceId: `{probe.evidence_id}`",
|
||||||
|
f" - traceId: `{probe.trace_id}`",
|
||||||
|
f" - auditId: `{probe.audit_id}`",
|
||||||
|
f" - requestHash: `{probe.request_hash}`",
|
||||||
|
f" - responseHash: `{probe.response_hash}`",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
lines.extend(["", "## Checks", ""])
|
||||||
|
for check in report.checks:
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
f"- `{check.rule_id}`: `{check.status.value}`",
|
||||||
|
f" - motivo: {check.reason}",
|
||||||
|
f" - proxima_acao: {check.next_action}",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
lines.extend(["", "## Blockers", ""])
|
||||||
|
if report.blockers:
|
||||||
|
lines.extend(f"- `{item}`" for item in report.blockers)
|
||||||
|
else:
|
||||||
|
lines.append("- Nenhum blocker tecnico na politica local.")
|
||||||
|
return "\n".join(lines).strip() + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def access_policy_artifact_records(project_root: Path) -> tuple[GeneratedFile, ...]:
|
||||||
|
"""Return semantic records for project-local policy artifacts."""
|
||||||
|
|
||||||
|
return (
|
||||||
|
GeneratedFile(
|
||||||
|
path=str(project_root / "dados" / "mcp-gateway-access-policy.json"),
|
||||||
|
description="Politica estruturada de acesso GPT/MCP ao gateway.",
|
||||||
|
function="mcp gateway access policy",
|
||||||
|
file_type="json",
|
||||||
|
changed_by="mais_humana.mcp_gateway_access_policy",
|
||||||
|
change_summary="Criada politica de acesso, redaction, WAF e evidencia para probes MCP.",
|
||||||
|
relation_to_order="0045_GERENCIAL__pactuar-politica-acesso-waf-gpt-mcp-gateway",
|
||||||
|
),
|
||||||
|
GeneratedFile(
|
||||||
|
path=str(project_root / "matrizes" / "mcp-gateway-access-policy.csv"),
|
||||||
|
description="Matriz de checks da politica GPT/MCP Gateway.",
|
||||||
|
function="mcp gateway access policy matrix",
|
||||||
|
file_type="csv",
|
||||||
|
changed_by="mais_humana.mcp_gateway_access_policy",
|
||||||
|
change_summary="Criada matriz de regras, status e evidencias de acesso.",
|
||||||
|
relation_to_order="0045_GERENCIAL__pactuar-politica-acesso-waf-gpt-mcp-gateway",
|
||||||
|
),
|
||||||
|
GeneratedFile(
|
||||||
|
path=str(project_root / "ecossistema" / "MCP-GATEWAY-ACCESS-POLICY.md"),
|
||||||
|
description="Relatorio humano da politica de acesso GPT/MCP Gateway.",
|
||||||
|
function="mcp gateway access policy report",
|
||||||
|
file_type="markdown",
|
||||||
|
changed_by="mais_humana.mcp_gateway_access_policy",
|
||||||
|
change_summary="Criado relatorio de politica para chamada GPT/MCP com evidencia redigida.",
|
||||||
|
relation_to_order="0045_GERENCIAL__pactuar-politica-acesso-waf-gpt-mcp-gateway",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def write_access_policy_artifacts(
|
||||||
|
report: McpGatewayAccessPolicyReport,
|
||||||
|
project_root: Path,
|
||||||
|
*,
|
||||||
|
central_platform_folder: Path | None = None,
|
||||||
|
) -> tuple[GeneratedFile, ...]:
|
||||||
|
"""Write policy artifacts, tolerating central ACL failures."""
|
||||||
|
|
||||||
|
records = list(access_policy_artifact_records(project_root))
|
||||||
|
targets: list[tuple[Path, str, GeneratedFile | None]] = [
|
||||||
|
(project_root / "dados" / "mcp-gateway-access-policy.json", json.dumps(report.to_dict(), ensure_ascii=False, indent=2, sort_keys=True), None),
|
||||||
|
(project_root / "matrizes" / "mcp-gateway-access-policy.csv", access_policy_csv(report), None),
|
||||||
|
(project_root / "ecossistema" / "MCP-GATEWAY-ACCESS-POLICY.md", access_policy_markdown(report), None),
|
||||||
|
]
|
||||||
|
if central_platform_folder is not None:
|
||||||
|
central_path = central_platform_folder / "reports" / "MCP-GATEWAY-ACCESS-POLICY__RODADA015.md"
|
||||||
|
central_record = GeneratedFile(
|
||||||
|
path=str(central_path),
|
||||||
|
description="Copia central da politica de acesso GPT/MCP Gateway.",
|
||||||
|
function="mcp gateway access policy central",
|
||||||
|
file_type="markdown",
|
||||||
|
changed_by="mais_humana.mcp_gateway_access_policy",
|
||||||
|
change_summary="Registrada politica de acesso na pasta central da plataforma 15.",
|
||||||
|
relation_to_order="0045_GERENCIAL__pactuar-politica-acesso-waf-gpt-mcp-gateway",
|
||||||
|
)
|
||||||
|
targets.append((central_path, access_policy_markdown(report), central_record))
|
||||||
|
central_failures: list[dict[str, str]] = []
|
||||||
|
for path, content, central_record in targets:
|
||||||
|
try:
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text(redact_sensitive_text(content), encoding="utf-8")
|
||||||
|
if central_record is not None:
|
||||||
|
records.append(central_record)
|
||||||
|
except OSError as exc:
|
||||||
|
if central_platform_folder is not None and central_platform_folder in path.parents:
|
||||||
|
central_failures.append({"path": str(path), "operation": "write_text", "error": f"{type(exc).__name__}: {exc}"})
|
||||||
|
continue
|
||||||
|
raise
|
||||||
|
if central_failures:
|
||||||
|
status_path = project_root / "dados" / "mcp-gateway-access-policy-central-write-status.json"
|
||||||
|
status_payload = {
|
||||||
|
"generatedAt": utc_now(),
|
||||||
|
"centralPlatformFolder": str(central_platform_folder) if central_platform_folder is not None else "",
|
||||||
|
"ok": False,
|
||||||
|
"failureCount": len(central_failures),
|
||||||
|
"failures": central_failures,
|
||||||
|
"policy": "falha de escrita central nao aborta artefatos do projeto real",
|
||||||
|
}
|
||||||
|
status_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
status_path.write_text(json.dumps(status_payload, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8")
|
||||||
|
records.append(
|
||||||
|
GeneratedFile(
|
||||||
|
path=str(status_path),
|
||||||
|
description="Status da escrita central da politica de acesso.",
|
||||||
|
function="mcp gateway access policy central write status",
|
||||||
|
file_type="json",
|
||||||
|
changed_by="mais_humana.mcp_gateway_access_policy",
|
||||||
|
change_summary="Registrada falha de escrita central sem abortar artefatos do projeto real.",
|
||||||
|
relation_to_order="0034_EXECUTIVA__corrigir-acl-escrita-central-e-sql-semantico-plataforma-15",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return tuple(records)
|
||||||
|
|
||||||
|
|
||||||
|
def run_access_policy_gate(
|
||||||
|
*,
|
||||||
|
project_root: Path,
|
||||||
|
central_platform_folder: Path | None = None,
|
||||||
|
publication_gate_json: Path | None = None,
|
||||||
|
) -> tuple[McpGatewayAccessPolicyReport, tuple[GeneratedFile, ...]]:
|
||||||
|
"""Build and write the access policy using latest publication-gate probes."""
|
||||||
|
|
||||||
|
gate_path = publication_gate_json or (project_root / "dados" / "mcp-publication-gate-mais-humana.json")
|
||||||
|
probes = read_publication_gate_probes(gate_path)
|
||||||
|
report = build_access_policy_report(probes=probes)
|
||||||
|
records = write_access_policy_artifacts(report, project_root, central_platform_folder=central_platform_folder)
|
||||||
|
return report, records
|
||||||
|
|
||||||
@@ -828,16 +828,14 @@ def write_publication_gate_artifacts(
|
|||||||
|
|
||||||
generated = list(publication_gate_artifact_records(project_root))
|
generated = list(publication_gate_artifact_records(project_root))
|
||||||
central_failures: list[dict[str, str]] = []
|
central_failures: list[dict[str, str]] = []
|
||||||
targets: list[tuple[Path, str]] = [
|
targets: list[tuple[Path, str, GeneratedFile | None]] = [
|
||||||
(project_root / "dados" / "mcp-publication-gate-mais-humana.json", json.dumps(report.to_dict(), ensure_ascii=False, indent=2, sort_keys=True)),
|
(project_root / "dados" / "mcp-publication-gate-mais-humana.json", json.dumps(report.to_dict(), ensure_ascii=False, indent=2, sort_keys=True), None),
|
||||||
(project_root / "matrizes" / "mcp-publication-gate-decisions.csv", publication_gate_csv(report)),
|
(project_root / "matrizes" / "mcp-publication-gate-decisions.csv", publication_gate_csv(report), None),
|
||||||
(project_root / "ecossistema" / "MCP-PUBLICATION-GATE-MAIS-HUMANA.md", publication_gate_markdown(report)),
|
(project_root / "ecossistema" / "MCP-PUBLICATION-GATE-MAIS-HUMANA.md", publication_gate_markdown(report), None),
|
||||||
]
|
]
|
||||||
if central_platform_folder is not None:
|
if central_platform_folder is not None:
|
||||||
central_path = central_platform_folder / "reports" / "executivos" / "MCP-PUBLICATION-GATE-MAIS-HUMANA__RODADA015.md"
|
central_path = central_platform_folder / "reports" / "executivos" / "MCP-PUBLICATION-GATE-MAIS-HUMANA__RODADA015.md"
|
||||||
targets.append((central_path, publication_gate_markdown(report)))
|
central_record = GeneratedFile(
|
||||||
generated.append(
|
|
||||||
GeneratedFile(
|
|
||||||
path=str(central_path),
|
path=str(central_path),
|
||||||
description="Copia central do gate de publicacao MCP Mais Humana.",
|
description="Copia central do gate de publicacao MCP Mais Humana.",
|
||||||
function="mcp publication gate central",
|
function="mcp publication gate central",
|
||||||
@@ -846,11 +844,13 @@ def write_publication_gate_artifacts(
|
|||||||
change_summary="Registrado gate de publicacao MCP na pasta central da plataforma 15.",
|
change_summary="Registrado gate de publicacao MCP na pasta central da plataforma 15.",
|
||||||
relation_to_order="015-ROTEADOR-PERMANENTE-DE-ORDEM_DE_SERVICO",
|
relation_to_order="015-ROTEADOR-PERMANENTE-DE-ORDEM_DE_SERVICO",
|
||||||
)
|
)
|
||||||
)
|
targets.append((central_path, publication_gate_markdown(report), central_record))
|
||||||
for path, content in targets:
|
for path, content, central_record in targets:
|
||||||
try:
|
try:
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
path.write_text(redact_sensitive_text(content), encoding="utf-8")
|
path.write_text(redact_sensitive_text(content), encoding="utf-8")
|
||||||
|
if central_record is not None:
|
||||||
|
generated.append(central_record)
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
if central_platform_folder is not None and central_platform_folder in path.parents:
|
if central_platform_folder is not None and central_platform_folder in path.parents:
|
||||||
central_failures.append(
|
central_failures.append(
|
||||||
|
|||||||
124
tests/test_mcp_gateway_access_policy.py
Normal file
124
tests/test_mcp_gateway_access_policy.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from mais_humana.cli import main
|
||||||
|
from mais_humana.mcp_gateway_access_policy import (
|
||||||
|
AccessPolicyStatus,
|
||||||
|
build_access_policy_report,
|
||||||
|
has_secret_shape,
|
||||||
|
probes_from_publication_gate_payload,
|
||||||
|
run_access_policy_gate,
|
||||||
|
)
|
||||||
|
from tests.helpers import make_tmp
|
||||||
|
|
||||||
|
|
||||||
|
def publication_gate_payload() -> dict[str, object]:
|
||||||
|
return {
|
||||||
|
"report": {
|
||||||
|
"live_probes": [
|
||||||
|
{
|
||||||
|
"tool_id": "mais_humana.rulebook.compact",
|
||||||
|
"endpoint": "https://mcps-gateway.ami-app.workers.dev/v1/execute",
|
||||||
|
"http_status": 200,
|
||||||
|
"ok": True,
|
||||||
|
"trace_id": "trace:mcps-gateway:actor:mais_humana.rulebook.compact",
|
||||||
|
"audit_id": "audit:mcps-gateway:actor:mais_humana.rulebook.compact",
|
||||||
|
"evidence_id": "evidence-rulebook",
|
||||||
|
"source_payload_hash": "hash-request-rulebook",
|
||||||
|
"source_records_hash": "hash-response-rulebook",
|
||||||
|
"response_excerpt": {"ok": "True", "providerId": "mais_humana"},
|
||||||
|
"observed_at": "2026-05-02T00:00:00+00:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tool_id": "mais_humana.admin_ui.same_source",
|
||||||
|
"endpoint": "https://mcps-gateway.ami-app.workers.dev/v1/execute",
|
||||||
|
"http_status": 200,
|
||||||
|
"ok": True,
|
||||||
|
"trace_id": "trace:mcps-gateway:actor:mais_humana.admin_ui.same_source",
|
||||||
|
"audit_id": "audit:mcps-gateway:actor:mais_humana.admin_ui.same_source",
|
||||||
|
"evidence_id": "evidence-same-source",
|
||||||
|
"source_payload_hash": "hash-request-same-source",
|
||||||
|
"source_records_hash": "hash-response-same-source",
|
||||||
|
"response_excerpt": {"ok": "True", "sameSource": "True"},
|
||||||
|
"observed_at": "2026-05-02T00:00:00+00:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tool_id": "mais_humana.mcp_transit.ledger",
|
||||||
|
"endpoint": "https://mcps-gateway.ami-app.workers.dev/v1/execute",
|
||||||
|
"http_status": 200,
|
||||||
|
"ok": True,
|
||||||
|
"trace_id": "trace:mcps-gateway:actor:mais_humana.mcp_transit.ledger",
|
||||||
|
"audit_id": "audit:mcps-gateway:actor:mais_humana.mcp_transit.ledger",
|
||||||
|
"evidence_id": "evidence-ledger",
|
||||||
|
"source_payload_hash": "hash-request-ledger",
|
||||||
|
"source_records_hash": "hash-response-ledger",
|
||||||
|
"response_excerpt": {"ok": "True", "records": "3"},
|
||||||
|
"observed_at": "2026-05-02T00:00:00+00:00",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class McpGatewayAccessPolicyTests(unittest.TestCase):
|
||||||
|
def test_policy_from_publication_gate_payload_passes_without_secret_leak(self) -> None:
|
||||||
|
probes = probes_from_publication_gate_payload(publication_gate_payload())
|
||||||
|
report = build_access_policy_report(probes=probes)
|
||||||
|
self.assertEqual(len(report.probes), 3)
|
||||||
|
self.assertEqual(report.status, AccessPolicyStatus.PASSED)
|
||||||
|
self.assertTrue(report.live_ready)
|
||||||
|
self.assertTrue(report.secret_safe)
|
||||||
|
self.assertFalse(report.blockers)
|
||||||
|
by_rule = {check.rule_id: check for check in report.checks}
|
||||||
|
self.assertEqual(by_rule["auth.bearer.present-redacted"].status, AccessPolicyStatus.PASSED)
|
||||||
|
self.assertEqual(by_rule["redaction.no-secret-shapes"].status, AccessPolicyStatus.PASSED)
|
||||||
|
|
||||||
|
def test_secret_shapes_block_redaction_rule(self) -> None:
|
||||||
|
self.assertTrue(has_secret_shape("Authorization: Bearer rawtoken123456"))
|
||||||
|
self.assertTrue(has_secret_shape("cfat_abc123"))
|
||||||
|
payload = publication_gate_payload()
|
||||||
|
live_probes = payload["report"]["live_probes"] # type: ignore[index]
|
||||||
|
live_probes[0]["response_excerpt"] = {"authorization": "Bearer rawtoken123456"} # type: ignore[index]
|
||||||
|
report = build_access_policy_report(probes=probes_from_publication_gate_payload(payload))
|
||||||
|
by_rule = {check.rule_id: check for check in report.checks}
|
||||||
|
self.assertEqual(by_rule["redaction.no-secret-shapes"].status, AccessPolicyStatus.BLOCKED)
|
||||||
|
|
||||||
|
def test_run_access_policy_gate_writes_project_and_central_artifacts(self) -> None:
|
||||||
|
tmp = make_tmp()
|
||||||
|
project = tmp / "tudo-para-ia-mais-humana"
|
||||||
|
central = tmp / "central" / "projects" / "15_repo_tudo-para-ia-mais-humana-platform"
|
||||||
|
gate_json = project / "dados" / "mcp-publication-gate-mais-humana.json"
|
||||||
|
gate_json.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
gate_json.write_text(json.dumps(publication_gate_payload()), encoding="utf-8")
|
||||||
|
report, records = run_access_policy_gate(project_root=project, central_platform_folder=central, publication_gate_json=gate_json)
|
||||||
|
self.assertEqual(report.status, AccessPolicyStatus.PASSED)
|
||||||
|
self.assertTrue((project / "dados" / "mcp-gateway-access-policy.json").exists())
|
||||||
|
self.assertTrue((project / "matrizes" / "mcp-gateway-access-policy.csv").exists())
|
||||||
|
self.assertTrue((central / "reports" / "MCP-GATEWAY-ACCESS-POLICY__RODADA015.md").exists())
|
||||||
|
self.assertGreaterEqual(len(records), 4)
|
||||||
|
|
||||||
|
def test_cli_access_policy_writes_payload(self) -> None:
|
||||||
|
tmp = make_tmp()
|
||||||
|
project = tmp / "tudo-para-ia-mais-humana"
|
||||||
|
gate_json = project / "dados" / "mcp-publication-gate-mais-humana.json"
|
||||||
|
gate_json.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
gate_json.write_text(json.dumps(publication_gate_payload()), encoding="utf-8")
|
||||||
|
code = main(
|
||||||
|
[
|
||||||
|
"mcp-access-policy",
|
||||||
|
"--project-root",
|
||||||
|
str(project),
|
||||||
|
"--publication-gate-json",
|
||||||
|
str(gate_json),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.assertEqual(code, 0)
|
||||||
|
payload = json.loads((project / "dados" / "mcp-gateway-access-policy.json").read_text(encoding="utf-8"))
|
||||||
|
self.assertEqual(payload["status"], "passed")
|
||||||
|
self.assertTrue(payload["secretSafe"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -113,6 +113,15 @@ class McpProviderContractTests(unittest.TestCase):
|
|||||||
self.assertGreater(len(ui_screens), 20)
|
self.assertGreater(len(ui_screens), 20)
|
||||||
self.assertTrue(all(contract.report_model_id for contract in report_models))
|
self.assertTrue(all(contract.report_model_id for contract in report_models))
|
||||||
|
|
||||||
|
def test_access_policy_contracts_cover_profiles_and_gateway_rules(self) -> None:
|
||||||
|
access_contracts = contracts_for_kind(McpContractKind.ACCESS_POLICY)
|
||||||
|
self.assertGreater(len(access_contracts), 500)
|
||||||
|
sample = access_contracts[0]
|
||||||
|
self.assertIn("authorizationCredentialRef", sample.required_payload_fields)
|
||||||
|
self.assertIn("pluginCloudflareDiagnosticIgnored", sample.required_payload_fields)
|
||||||
|
self.assertTrue(any("WAF" in step or "waf" in step for step in sample.validation_steps))
|
||||||
|
self.assertTrue(all("0045_GERENCIAL__pactuar-politica-acesso-waf-gpt-mcp-gateway" in item.order_ids for item in access_contracts))
|
||||||
|
|
||||||
def test_cli_mcp_provider_returns_json(self) -> None:
|
def test_cli_mcp_provider_returns_json(self) -> None:
|
||||||
root = make_tmp()
|
root = make_tmp()
|
||||||
self.make_repo(
|
self.make_repo(
|
||||||
|
|||||||
@@ -141,6 +141,48 @@ def redaction_requirements(platform_id: str) -> tuple[str, ...]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ACCESS_POLICY_SURFACES = (
|
||||||
|
("gpt-execute-probe", "governance", "mais_humana.gateway.access_policy.gpt_probe"),
|
||||||
|
("admin-ui-render", "experience", "mais_humana.gateway.access_policy.admin_ui"),
|
||||||
|
("automation-smoke", "observability", "mais_humana.gateway.access_policy.smoke"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def unique_tuple(values: Iterable[object]) -> tuple[str, ...]:
|
||||||
|
seen: set[str] = set()
|
||||||
|
output: list[str] = []
|
||||||
|
for value in values:
|
||||||
|
text = str(value)
|
||||||
|
if not text or text in seen:
|
||||||
|
continue
|
||||||
|
seen.add(text)
|
||||||
|
output.append(text)
|
||||||
|
return tuple(output)
|
||||||
|
|
||||||
|
|
||||||
|
def access_policy_payload_fields(platform_id: str, profile_id: str, surface: str, category: str) -> tuple[str, ...]:
|
||||||
|
return unique_tuple(
|
||||||
|
payload_fields(platform_id, profile_id, surface, category)
|
||||||
|
+ (
|
||||||
|
"httpMethod",
|
||||||
|
"contentType",
|
||||||
|
"userAgent",
|
||||||
|
"authorizationCredentialRef",
|
||||||
|
"authorizationRawPersisted",
|
||||||
|
"wafDecision",
|
||||||
|
"wafRuleId",
|
||||||
|
"rateLimitPerMinute",
|
||||||
|
"logRetentionDays",
|
||||||
|
"requestHash",
|
||||||
|
"responseHash",
|
||||||
|
"redactionPolicyId",
|
||||||
|
"secretSafe",
|
||||||
|
"pluginCloudflareDiagnosticIgnored",
|
||||||
|
"wranglerOperationalReference",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validation_steps(platform_id: str, profile_id: str, surface: str, kind: str) -> tuple[str, ...]:
|
def validation_steps(platform_id: str, profile_id: str, surface: str, kind: str) -> tuple[str, ...]:
|
||||||
return (
|
return (
|
||||||
f"chamar {platform_id} somente via tudo-para-ia-mcps-internos-plataform",
|
f"chamar {platform_id} somente via tudo-para-ia-mcps-internos-plataform",
|
||||||
@@ -152,6 +194,17 @@ def validation_steps(platform_id: str, profile_id: str, surface: str, kind: str)
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def access_policy_validations(platform_id: str, profile_id: str, surface: str) -> tuple[str, ...]:
|
||||||
|
return (
|
||||||
|
f"executar {surface} de {platform_id}/{profile_id} por /v1/execute com POST application/json",
|
||||||
|
"confirmar User-Agent operacional e separar WAF de erro runtime",
|
||||||
|
"confirmar Authorization via credentialRef sem persistir bearer bruto",
|
||||||
|
"validar traceId, auditId, requestHash e responseHash",
|
||||||
|
"validar redaction contra cfat_, bearer bruto e tokens numericos longos",
|
||||||
|
"registrar rate limit, retencao de logs e decisao WAF no MCP",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def contract_block(
|
def contract_block(
|
||||||
name: str,
|
name: str,
|
||||||
*,
|
*,
|
||||||
@@ -398,6 +451,50 @@ def build_contracts() -> tuple[list[str], list[str]]:
|
|||||||
)
|
)
|
||||||
index += 1
|
index += 1
|
||||||
|
|
||||||
|
for profile in HUMAN_PROFILES:
|
||||||
|
profile_categories = category_values(profile.priority_needs)
|
||||||
|
audience = audience_for_profile(profile.profile_id, profile_categories)
|
||||||
|
for surface, category, tool_id in ACCESS_POLICY_SURFACES:
|
||||||
|
name = f"CONTRACT_{index:04d}"
|
||||||
|
names.append(name)
|
||||||
|
blocks.append(
|
||||||
|
contract_block(
|
||||||
|
name,
|
||||||
|
contract_id=f"{platform.platform_id}.{profile.profile_id}.{surface}.access-policy",
|
||||||
|
kind="ACCESS_POLICY",
|
||||||
|
platform_id=platform.platform_id,
|
||||||
|
profile_id=profile.profile_id,
|
||||||
|
tool_id=tool_id,
|
||||||
|
title=f"Politica de acesso {surface} para {platform.title} e {profile.name}",
|
||||||
|
purpose=(
|
||||||
|
f"Garantir que chamadas GPT/MCP de {platform.title} para {profile.name} "
|
||||||
|
"usem headers, bearer redigido, WAF classificado, hashes e ledger auditavel."
|
||||||
|
),
|
||||||
|
source_tool_id="mais_humana.gateway.access_policy",
|
||||||
|
payload=access_policy_payload_fields(platform.platform_id, profile.profile_id, surface, category),
|
||||||
|
truth=truth,
|
||||||
|
panel_ready=panel_ready,
|
||||||
|
gpt_explainable=True,
|
||||||
|
report_model_id=f"access.{platform.platform_id}.{profile.profile_id}.{normalize(surface)}",
|
||||||
|
audience=audience,
|
||||||
|
redaction=redaction_requirements(platform.platform_id)
|
||||||
|
+ (
|
||||||
|
"bloquear persistencia de Authorization Bearer bruto",
|
||||||
|
"registrar apenas credentialRef e hashes de evidencia",
|
||||||
|
),
|
||||||
|
validations=access_policy_validations(platform.platform_id, profile.profile_id, surface),
|
||||||
|
pending=f"homologar politica de acesso {surface} para {platform.platform_id}/{profile.profile_id}",
|
||||||
|
order_ids=(
|
||||||
|
"0032_EXECUTIVA__validar-live-tools-mais-humana-v1-execute-com-evidencia",
|
||||||
|
"0045_GERENCIAL__pactuar-politica-acesso-waf-gpt-mcp-gateway",
|
||||||
|
),
|
||||||
|
policy_tags=("access_policy", "waf", "redaction", "same_source", normalize(surface)),
|
||||||
|
maturity=max(7, maturity),
|
||||||
|
generated_from="platform_profile_access_policy_contract",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
index += 1
|
||||||
|
|
||||||
special_contracts = [
|
special_contracts = [
|
||||||
(
|
(
|
||||||
"docs.formal-exception.docs-catalogonly",
|
"docs.formal-exception.docs-catalogonly",
|
||||||
|
|||||||
Reference in New Issue
Block a user