auto-sync: tudo-para-ia-mais-humana 2026-05-02 03:04:41
This commit is contained in:
673
src/mais_humana/mcp_admin_route_acceptance.py
Normal file
673
src/mais_humana/mcp_admin_route_acceptance.py
Normal file
@@ -0,0 +1,673 @@
|
||||
"""Acceptance catalog for MCP-only administration routes.
|
||||
|
||||
The control contracts already define the administrative routes that must pass
|
||||
through ``tudo-para-ia-mcps-internos-plataform``. This module turns the
|
||||
generated contracts into a compact, executable acceptance layer: the gateway,
|
||||
GPT, UI renderer, central reports, and service-order closeout can all read the
|
||||
same case list without traversing the large contract object graph.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import io
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable, Mapping, Sequence
|
||||
|
||||
from .mcp_contract import MCP_CONTROL_PLANE_ID, PROVIDER_ID, stable_hash
|
||||
from .models import GeneratedFile, as_plain_data, merge_unique, utc_now
|
||||
from .redaction import redact_sensitive_text
|
||||
|
||||
|
||||
try:
|
||||
from .generated_mcp_admin_route_acceptance import (
|
||||
ADMIN_ROUTE_ACCEPTANCE_CASES as GENERATED_ADMIN_ROUTE_ACCEPTANCE_CASES,
|
||||
SOURCE_HASH as GENERATED_SOURCE_HASH,
|
||||
)
|
||||
except ImportError: # pragma: no cover - exercised before the generator runs.
|
||||
GENERATED_ADMIN_ROUTE_ACCEPTANCE_CASES: tuple[Mapping[str, Any], ...] = ()
|
||||
GENERATED_SOURCE_HASH = ""
|
||||
|
||||
|
||||
class AdminRouteAcceptanceStatus(str, Enum):
|
||||
"""Acceptance status for one generated route case."""
|
||||
|
||||
READY = "ready"
|
||||
PARTIAL = "partial"
|
||||
BLOCKED = "blocked"
|
||||
|
||||
|
||||
class AdminRouteOperation(str, Enum):
|
||||
"""Canonical administration operation names."""
|
||||
|
||||
CONSULTA = "consulta"
|
||||
DIAGNOSTICO = "diagnostico"
|
||||
ACAO = "acao"
|
||||
AUDITORIA = "auditoria"
|
||||
EXPLICACAO = "explicacao"
|
||||
|
||||
|
||||
MANDATORY_TRANSIT_FIELDS = (
|
||||
"origin",
|
||||
"destination",
|
||||
"tool",
|
||||
"payload",
|
||||
"actor",
|
||||
"permission",
|
||||
"result",
|
||||
"traceId",
|
||||
"auditId",
|
||||
"timestamp",
|
||||
)
|
||||
|
||||
MANDATORY_PAYLOAD_FIELDS = (
|
||||
"adminRouteId",
|
||||
"adminRouteKind",
|
||||
"sourceEndpoint",
|
||||
"sourceToolId",
|
||||
"sourcePayloadHash",
|
||||
"sourceRecordsHash",
|
||||
"truthState",
|
||||
"panelReady",
|
||||
"gptExplainable",
|
||||
"humanNextAction",
|
||||
"permissionScope",
|
||||
"mcpOnlyAdministration",
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class AdminRouteAcceptanceCase:
|
||||
"""One MCP-only administration route acceptance case."""
|
||||
|
||||
route_id: str
|
||||
platform_id: str
|
||||
profile_id: str
|
||||
operation: str
|
||||
tool_id: str
|
||||
source_tool_id: str
|
||||
permission_scope: str
|
||||
source_endpoint: str
|
||||
source_payload_hash: str
|
||||
source_records_hash: str
|
||||
evidence_id: str
|
||||
truth_state: str
|
||||
panel_ready: bool
|
||||
gpt_explainable: bool
|
||||
same_source: bool
|
||||
approval_required: bool
|
||||
dry_run_supported: bool
|
||||
required_transit_fields: tuple[str, ...]
|
||||
required_payload_fields: tuple[str, ...]
|
||||
validation_steps: tuple[str, ...]
|
||||
redaction_requirements: tuple[str, ...]
|
||||
pending_if_missing: str
|
||||
order_ids: tuple[str, ...]
|
||||
policy_tags: tuple[str, ...]
|
||||
maturity_level: int
|
||||
status: AdminRouteAcceptanceStatus
|
||||
blocker_reasons: tuple[str, ...]
|
||||
|
||||
@property
|
||||
def route_key(self) -> str:
|
||||
return f"{self.platform_id}/{self.profile_id}/{self.operation}"
|
||||
|
||||
@property
|
||||
def ready(self) -> bool:
|
||||
return self.status == AdminRouteAcceptanceStatus.READY
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return as_plain_data(self)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class AdminRouteAcceptanceSummary:
|
||||
"""Aggregated acceptance result for all generated administration routes."""
|
||||
|
||||
generated_at: str
|
||||
provider_id: str
|
||||
control_plane_id: str
|
||||
source_hash: str
|
||||
total_cases: int
|
||||
ready_cases: int
|
||||
partial_cases: int
|
||||
blocked_cases: int
|
||||
platforms_count: int
|
||||
profiles_count: int
|
||||
operations: Mapping[str, int]
|
||||
platforms: Mapping[str, int]
|
||||
blockers: tuple[str, ...]
|
||||
evidence_id: str
|
||||
|
||||
@property
|
||||
def status(self) -> AdminRouteAcceptanceStatus:
|
||||
if self.blocked_cases:
|
||||
return AdminRouteAcceptanceStatus.BLOCKED
|
||||
if self.partial_cases:
|
||||
return AdminRouteAcceptanceStatus.PARTIAL
|
||||
return AdminRouteAcceptanceStatus.READY if self.total_cases else AdminRouteAcceptanceStatus.BLOCKED
|
||||
|
||||
@property
|
||||
def ready_ratio(self) -> float:
|
||||
if not self.total_cases:
|
||||
return 0.0
|
||||
return self.ready_cases / self.total_cases
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
data = as_plain_data(self)
|
||||
data["status"] = self.status.value
|
||||
data["readyRatio"] = self.ready_ratio
|
||||
return data
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class AdminRouteAcceptanceReport:
|
||||
"""Full acceptance report with a bounded sample and summary."""
|
||||
|
||||
report_id: str
|
||||
generated_at: str
|
||||
summary: AdminRouteAcceptanceSummary
|
||||
returned_cases: tuple[AdminRouteAcceptanceCase, ...]
|
||||
sample_limit: int
|
||||
filters: Mapping[str, str]
|
||||
generated_files: tuple[str, ...] = ()
|
||||
|
||||
@property
|
||||
def status(self) -> AdminRouteAcceptanceStatus:
|
||||
return self.summary.status
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
data = as_plain_data(self)
|
||||
data["status"] = self.status.value
|
||||
return data
|
||||
|
||||
|
||||
def _tuple(value: object) -> tuple[str, ...]:
|
||||
if value is None:
|
||||
return ()
|
||||
if isinstance(value, str):
|
||||
return (value,)
|
||||
if isinstance(value, Iterable):
|
||||
return tuple(str(item) for item in value)
|
||||
return (str(value),)
|
||||
|
||||
|
||||
def _bool(value: object) -> bool:
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
return str(value).strip().lower() in {"1", "true", "yes", "sim"}
|
||||
|
||||
|
||||
def _status(value: object) -> AdminRouteAcceptanceStatus:
|
||||
text = str(value or "").strip()
|
||||
for status in AdminRouteAcceptanceStatus:
|
||||
if status.value == text:
|
||||
return status
|
||||
return AdminRouteAcceptanceStatus.BLOCKED
|
||||
|
||||
|
||||
def case_from_mapping(row: Mapping[str, Any]) -> AdminRouteAcceptanceCase:
|
||||
"""Convert generated plain data into a typed acceptance case."""
|
||||
|
||||
return AdminRouteAcceptanceCase(
|
||||
route_id=str(row.get("routeId") or row.get("route_id") or ""),
|
||||
platform_id=str(row.get("platformId") or row.get("platform_id") or ""),
|
||||
profile_id=str(row.get("profileId") or row.get("profile_id") or ""),
|
||||
operation=str(row.get("operation") or ""),
|
||||
tool_id=str(row.get("toolId") or row.get("tool_id") or ""),
|
||||
source_tool_id=str(row.get("sourceToolId") or row.get("source_tool_id") or ""),
|
||||
permission_scope=str(row.get("permissionScope") or row.get("permission_scope") or ""),
|
||||
source_endpoint=str(row.get("sourceEndpoint") or row.get("source_endpoint") or ""),
|
||||
source_payload_hash=str(row.get("sourcePayloadHash") or row.get("source_payload_hash") or ""),
|
||||
source_records_hash=str(row.get("sourceRecordsHash") or row.get("source_records_hash") or ""),
|
||||
evidence_id=str(row.get("evidenceId") or row.get("evidence_id") or ""),
|
||||
truth_state=str(row.get("truthState") or row.get("truth_state") or ""),
|
||||
panel_ready=_bool(row.get("panelReady") or row.get("panel_ready")),
|
||||
gpt_explainable=_bool(row.get("gptExplainable") or row.get("gpt_explainable")),
|
||||
same_source=_bool(row.get("sameSource") or row.get("same_source")),
|
||||
approval_required=_bool(row.get("approvalRequired") or row.get("approval_required")),
|
||||
dry_run_supported=_bool(row.get("dryRunSupported") or row.get("dry_run_supported")),
|
||||
required_transit_fields=_tuple(row.get("requiredTransitFields") or row.get("required_transit_fields")),
|
||||
required_payload_fields=_tuple(row.get("requiredPayloadFields") or row.get("required_payload_fields")),
|
||||
validation_steps=_tuple(row.get("validationSteps") or row.get("validation_steps")),
|
||||
redaction_requirements=_tuple(row.get("redactionRequirements") or row.get("redaction_requirements")),
|
||||
pending_if_missing=str(row.get("pendingIfMissing") or row.get("pending_if_missing") or ""),
|
||||
order_ids=_tuple(row.get("orderIds") or row.get("order_ids")),
|
||||
policy_tags=_tuple(row.get("policyTags") or row.get("policy_tags")),
|
||||
maturity_level=int(row.get("maturityLevel") or row.get("maturity_level") or 0),
|
||||
status=_status(row.get("status")),
|
||||
blocker_reasons=_tuple(row.get("blockerReasons") or row.get("blocker_reasons")),
|
||||
)
|
||||
|
||||
|
||||
def iter_admin_route_acceptance_cases(
|
||||
*,
|
||||
platform_id: str | None = None,
|
||||
profile_id: str | None = None,
|
||||
operation: str | None = None,
|
||||
status: str | None = None,
|
||||
limit: int | None = None,
|
||||
) -> tuple[AdminRouteAcceptanceCase, ...]:
|
||||
"""Return generated acceptance cases, optionally filtered."""
|
||||
|
||||
output: list[AdminRouteAcceptanceCase] = []
|
||||
for raw in GENERATED_ADMIN_ROUTE_ACCEPTANCE_CASES:
|
||||
case = case_from_mapping(raw)
|
||||
if platform_id and case.platform_id != platform_id:
|
||||
continue
|
||||
if profile_id and case.profile_id != profile_id:
|
||||
continue
|
||||
if operation and case.operation != operation:
|
||||
continue
|
||||
if status and case.status.value != status:
|
||||
continue
|
||||
output.append(case)
|
||||
if limit is not None and len(output) >= max(0, int(limit)):
|
||||
break
|
||||
return tuple(output)
|
||||
|
||||
|
||||
def _count_by(cases: Sequence[AdminRouteAcceptanceCase], attr: str) -> dict[str, int]:
|
||||
counts: dict[str, int] = {}
|
||||
for case in cases:
|
||||
value = str(getattr(case, attr))
|
||||
counts[value] = counts.get(value, 0) + 1
|
||||
return dict(sorted(counts.items()))
|
||||
|
||||
|
||||
def summarize_admin_route_acceptance(cases: Sequence[AdminRouteAcceptanceCase] | None = None) -> AdminRouteAcceptanceSummary:
|
||||
"""Build a compact readiness summary for MCP-only administration routes."""
|
||||
|
||||
case_set = tuple(cases if cases is not None else iter_admin_route_acceptance_cases())
|
||||
blockers = merge_unique(
|
||||
f"{case.route_id}:{reason}"
|
||||
for case in case_set
|
||||
for reason in case.blocker_reasons
|
||||
if case.status != AdminRouteAcceptanceStatus.READY
|
||||
)
|
||||
evidence_id = "evidence-" + stable_hash(
|
||||
{
|
||||
"sourceHash": GENERATED_SOURCE_HASH,
|
||||
"caseCount": len(case_set),
|
||||
"ready": sum(1 for case in case_set if case.status == AdminRouteAcceptanceStatus.READY),
|
||||
"blockers": blockers,
|
||||
}
|
||||
)[:24]
|
||||
return AdminRouteAcceptanceSummary(
|
||||
generated_at=utc_now(),
|
||||
provider_id=PROVIDER_ID,
|
||||
control_plane_id=MCP_CONTROL_PLANE_ID,
|
||||
source_hash=GENERATED_SOURCE_HASH,
|
||||
total_cases=len(case_set),
|
||||
ready_cases=sum(1 for case in case_set if case.status == AdminRouteAcceptanceStatus.READY),
|
||||
partial_cases=sum(1 for case in case_set if case.status == AdminRouteAcceptanceStatus.PARTIAL),
|
||||
blocked_cases=sum(1 for case in case_set if case.status == AdminRouteAcceptanceStatus.BLOCKED),
|
||||
platforms_count=len({case.platform_id for case in case_set}),
|
||||
profiles_count=len({case.profile_id for case in case_set}),
|
||||
operations=_count_by(case_set, "operation"),
|
||||
platforms=_count_by(case_set, "platform_id"),
|
||||
blockers=blockers[:80],
|
||||
evidence_id=evidence_id,
|
||||
)
|
||||
|
||||
|
||||
def build_admin_route_acceptance_report(
|
||||
*,
|
||||
platform_id: str | None = None,
|
||||
profile_id: str | None = None,
|
||||
operation: str | None = None,
|
||||
status: str | None = None,
|
||||
limit: int = 80,
|
||||
) -> AdminRouteAcceptanceReport:
|
||||
"""Build a report with full summary and bounded returned cases."""
|
||||
|
||||
all_filtered = iter_admin_route_acceptance_cases(
|
||||
platform_id=platform_id,
|
||||
profile_id=profile_id,
|
||||
operation=operation,
|
||||
status=status,
|
||||
)
|
||||
returned = all_filtered[: max(0, int(limit))]
|
||||
summary = summarize_admin_route_acceptance(all_filtered)
|
||||
filter_payload = {
|
||||
"platformId": platform_id or "",
|
||||
"profileId": profile_id or "",
|
||||
"operation": operation or "",
|
||||
"status": status or "",
|
||||
}
|
||||
report_id = "mcp-admin-route-acceptance-" + stable_hash(
|
||||
{
|
||||
"summaryEvidence": summary.evidence_id,
|
||||
"filters": filter_payload,
|
||||
"limit": limit,
|
||||
}
|
||||
)[:16]
|
||||
return AdminRouteAcceptanceReport(
|
||||
report_id=report_id,
|
||||
generated_at=utc_now(),
|
||||
summary=summary,
|
||||
returned_cases=tuple(returned),
|
||||
sample_limit=max(0, int(limit)),
|
||||
filters=filter_payload,
|
||||
)
|
||||
|
||||
|
||||
def compact_acceptance_payload(report: AdminRouteAcceptanceReport) -> dict[str, Any]:
|
||||
"""Return a compact payload for GPT/MCP/UI discovery."""
|
||||
|
||||
return {
|
||||
"reportId": report.report_id,
|
||||
"generatedAt": report.generated_at,
|
||||
"providerId": report.summary.provider_id,
|
||||
"controlPlaneId": report.summary.control_plane_id,
|
||||
"status": report.status.value,
|
||||
"sourceHash": report.summary.source_hash,
|
||||
"totalCases": report.summary.total_cases,
|
||||
"readyCases": report.summary.ready_cases,
|
||||
"partialCases": report.summary.partial_cases,
|
||||
"blockedCases": report.summary.blocked_cases,
|
||||
"readyRatio": report.summary.ready_ratio,
|
||||
"operations": dict(report.summary.operations),
|
||||
"platformsCount": report.summary.platforms_count,
|
||||
"profilesCount": report.summary.profiles_count,
|
||||
"evidenceId": report.summary.evidence_id,
|
||||
"returnedCases": len(report.returned_cases),
|
||||
"cases": [
|
||||
{
|
||||
"routeId": case.route_id,
|
||||
"platformId": case.platform_id,
|
||||
"profileId": case.profile_id,
|
||||
"operation": case.operation,
|
||||
"toolId": case.tool_id,
|
||||
"permissionScope": case.permission_scope,
|
||||
"sameSource": case.same_source,
|
||||
"status": case.status.value,
|
||||
"evidenceId": case.evidence_id,
|
||||
"sourcePayloadHash": case.source_payload_hash,
|
||||
"sourceRecordsHash": case.source_records_hash,
|
||||
"humanNextAction": case.pending_if_missing,
|
||||
}
|
||||
for case in report.returned_cases
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def acceptance_csv(report: AdminRouteAcceptanceReport) -> str:
|
||||
"""Render acceptance cases as CSV."""
|
||||
|
||||
rows = [
|
||||
[
|
||||
"route_id",
|
||||
"platform_id",
|
||||
"profile_id",
|
||||
"operation",
|
||||
"tool_id",
|
||||
"permission_scope",
|
||||
"same_source",
|
||||
"status",
|
||||
"approval_required",
|
||||
"dry_run_supported",
|
||||
"evidence_id",
|
||||
"blockers",
|
||||
]
|
||||
]
|
||||
for case in report.returned_cases:
|
||||
rows.append(
|
||||
[
|
||||
case.route_id,
|
||||
case.platform_id,
|
||||
case.profile_id,
|
||||
case.operation,
|
||||
case.tool_id,
|
||||
case.permission_scope,
|
||||
"yes" if case.same_source else "no",
|
||||
case.status.value,
|
||||
"yes" if case.approval_required else "no",
|
||||
"yes" if case.dry_run_supported else "no",
|
||||
case.evidence_id,
|
||||
"; ".join(case.blocker_reasons),
|
||||
]
|
||||
)
|
||||
buffer = io.StringIO()
|
||||
writer = csv.writer(buffer, lineterminator="\n")
|
||||
writer.writerows(rows)
|
||||
return buffer.getvalue()
|
||||
|
||||
|
||||
def acceptance_markdown(report: AdminRouteAcceptanceReport) -> str:
|
||||
"""Render a human-readable route acceptance report."""
|
||||
|
||||
lines = [
|
||||
"# MCP Admin Route Acceptance",
|
||||
"",
|
||||
f"- report_id: `{report.report_id}`",
|
||||
f"- generated_at: `{report.generated_at}`",
|
||||
f"- provider_id: `{report.summary.provider_id}`",
|
||||
f"- control_plane_id: `{report.summary.control_plane_id}`",
|
||||
f"- status: `{report.status.value}`",
|
||||
f"- source_hash: `{report.summary.source_hash}`",
|
||||
f"- total_cases: `{report.summary.total_cases}`",
|
||||
f"- ready_cases: `{report.summary.ready_cases}`",
|
||||
f"- partial_cases: `{report.summary.partial_cases}`",
|
||||
f"- blocked_cases: `{report.summary.blocked_cases}`",
|
||||
f"- ready_ratio: `{report.summary.ready_ratio:.4f}`",
|
||||
f"- evidence_id: `{report.summary.evidence_id}`",
|
||||
"",
|
||||
"## Operacoes",
|
||||
"",
|
||||
]
|
||||
if report.summary.operations:
|
||||
lines.extend(f"- `{name}`: `{count}`" for name, count in sorted(report.summary.operations.items()))
|
||||
else:
|
||||
lines.append("- Nenhuma rota administrativa materializada.")
|
||||
lines.extend(["", "## Plataformas", ""])
|
||||
if report.summary.platforms:
|
||||
lines.extend(f"- `{name}`: `{count}`" for name, count in sorted(report.summary.platforms.items()))
|
||||
else:
|
||||
lines.append("- Nenhuma plataforma materializada.")
|
||||
lines.extend(["", "## Amostra", ""])
|
||||
for case in report.returned_cases[:30]:
|
||||
lines.extend(
|
||||
[
|
||||
f"### {case.route_id}",
|
||||
"",
|
||||
f"- route_key: `{case.route_key}`",
|
||||
f"- tool_id: `{case.tool_id}`",
|
||||
f"- source_tool_id: `{case.source_tool_id}`",
|
||||
f"- permission_scope: `{case.permission_scope}`",
|
||||
f"- same_source: `{case.same_source}`",
|
||||
f"- status: `{case.status.value}`",
|
||||
f"- evidence_id: `{case.evidence_id}`",
|
||||
f"- source_payload_hash: `{case.source_payload_hash}`",
|
||||
f"- source_records_hash: `{case.source_records_hash}`",
|
||||
f"- next_action: {case.pending_if_missing}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
lines.extend(["## Bloqueios", ""])
|
||||
if report.summary.blockers:
|
||||
lines.extend(f"- `{redact_sensitive_text(item)}`" for item in report.summary.blockers[:80])
|
||||
else:
|
||||
lines.append("- Nenhum bloqueio de contrato MCP-only nas rotas administrativas geradas.")
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"## Regra operacional",
|
||||
"",
|
||||
"- Toda consulta, diagnostico, acao, auditoria e explicacao interplataforma deve transitar pelo MCPs Internos.",
|
||||
"- Cada caso preserva origin, destination, tool, payload, actor, permission, result, traceId, auditId e timestamp.",
|
||||
"- Valores de segredo bruto nao fazem parte deste catalogo; evidencias usam hashes, traceId, auditId e refs opacas.",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines).strip() + "\n"
|
||||
|
||||
|
||||
def acceptance_artifact_records(project_root: Path, central_platform_folder: Path | None = None) -> tuple[GeneratedFile, ...]:
|
||||
"""Return semantic records for acceptance artifacts."""
|
||||
|
||||
records = [
|
||||
GeneratedFile(
|
||||
path=str(project_root / "dados" / "mcp-admin-route-acceptance.json"),
|
||||
description="Catalogo estruturado de aceitacao das rotas administrativas MCP-only.",
|
||||
function="mcp admin route acceptance report",
|
||||
file_type="json",
|
||||
changed_by="mais_humana.mcp_admin_route_acceptance",
|
||||
change_summary="Materializado catalogo auditavel de rotas administrativas MCP-only.",
|
||||
relation_to_order="0037_EXECUTIVA__homologar-rotas-administrativas-mcp-no-gateway",
|
||||
),
|
||||
GeneratedFile(
|
||||
path=str(project_root / "dados" / "mcp-admin-route-acceptance-compacto.json"),
|
||||
description="Resumo compacto das rotas administrativas para GPT/MCP/UI.",
|
||||
function="mcp admin route acceptance compact payload",
|
||||
file_type="json",
|
||||
changed_by="mais_humana.mcp_admin_route_acceptance",
|
||||
change_summary="Criado payload compacto com contadores, hashes e amostra de rotas.",
|
||||
relation_to_order="0037_EXECUTIVA__homologar-rotas-administrativas-mcp-no-gateway",
|
||||
),
|
||||
GeneratedFile(
|
||||
path=str(project_root / "matrizes" / "mcp-admin-route-acceptance.csv"),
|
||||
description="Matriz tabular de aceitacao das rotas administrativas MCP-only.",
|
||||
function="mcp admin route acceptance matrix",
|
||||
file_type="csv",
|
||||
changed_by="mais_humana.mcp_admin_route_acceptance",
|
||||
change_summary="Criada matriz para auditoria de operacao, permissao e same-source.",
|
||||
relation_to_order="0037_EXECUTIVA__homologar-rotas-administrativas-mcp-no-gateway",
|
||||
),
|
||||
GeneratedFile(
|
||||
path=str(project_root / "ecossistema" / "MCP-ADMIN-ROUTE-ACCEPTANCE.md"),
|
||||
description="Relatorio humano de aceitacao das rotas administrativas MCP-only.",
|
||||
function="mcp admin route acceptance human report",
|
||||
file_type="markdown",
|
||||
changed_by="mais_humana.mcp_admin_route_acceptance",
|
||||
change_summary="Criado relatorio de homologacao local das rotas administrativas.",
|
||||
relation_to_order="0037_EXECUTIVA__homologar-rotas-administrativas-mcp-no-gateway",
|
||||
),
|
||||
]
|
||||
if central_platform_folder is not None:
|
||||
records.extend(
|
||||
[
|
||||
GeneratedFile(
|
||||
path=str(central_platform_folder / "reports" / "EXECUTADO__mcp-admin-route-acceptance.md"),
|
||||
description="Copia central do relatorio de aceitacao das rotas administrativas.",
|
||||
function="mcp admin route acceptance central report",
|
||||
file_type="markdown",
|
||||
changed_by="mais_humana.mcp_admin_route_acceptance",
|
||||
change_summary="Registrada homologacao local das rotas administrativas na central.",
|
||||
relation_to_order="0037_EXECUTIVA__homologar-rotas-administrativas-mcp-no-gateway",
|
||||
),
|
||||
GeneratedFile(
|
||||
path=str(central_platform_folder / "indexes" / "mcp-admin-route-acceptance-index.md"),
|
||||
description="Indice central do catalogo de rotas administrativas MCP-only.",
|
||||
function="mcp admin route acceptance central index",
|
||||
file_type="markdown",
|
||||
changed_by="mais_humana.mcp_admin_route_acceptance",
|
||||
change_summary="Indexado status, contadores e evidencia da aceitacao MCP-only.",
|
||||
relation_to_order="0037_EXECUTIVA__homologar-rotas-administrativas-mcp-no-gateway",
|
||||
),
|
||||
]
|
||||
)
|
||||
return tuple(records)
|
||||
|
||||
|
||||
def _write_text(path: Path, text: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(text, encoding="utf-8")
|
||||
|
||||
|
||||
def write_admin_route_acceptance_artifacts(
|
||||
report: AdminRouteAcceptanceReport,
|
||||
project_root: Path,
|
||||
*,
|
||||
central_platform_folder: Path | None = None,
|
||||
) -> tuple[GeneratedFile, ...]:
|
||||
"""Write project and optional central route acceptance artifacts."""
|
||||
|
||||
full_json = json.dumps(report.to_dict(), ensure_ascii=False, indent=2, sort_keys=True)
|
||||
compact_json = json.dumps(compact_acceptance_payload(report), ensure_ascii=False, indent=2, sort_keys=True)
|
||||
markdown = acceptance_markdown(report)
|
||||
targets: list[tuple[Path, str]] = [
|
||||
(project_root / "dados" / "mcp-admin-route-acceptance.json", full_json),
|
||||
(project_root / "dados" / "mcp-admin-route-acceptance-compacto.json", compact_json),
|
||||
(project_root / "matrizes" / "mcp-admin-route-acceptance.csv", acceptance_csv(report)),
|
||||
(project_root / "ecossistema" / "MCP-ADMIN-ROUTE-ACCEPTANCE.md", markdown),
|
||||
]
|
||||
records = list(acceptance_artifact_records(project_root, central_platform_folder))
|
||||
central_failures: list[dict[str, str]] = []
|
||||
if central_platform_folder is not None:
|
||||
targets.extend(
|
||||
[
|
||||
(central_platform_folder / "reports" / "EXECUTADO__mcp-admin-route-acceptance.md", markdown),
|
||||
(central_platform_folder / "indexes" / "mcp-admin-route-acceptance-index.md", markdown),
|
||||
]
|
||||
)
|
||||
for path, content in targets:
|
||||
try:
|
||||
_write_text(path, content)
|
||||
except OSError as exc:
|
||||
if central_platform_folder is not None and central_platform_folder in path.parents:
|
||||
central_failures.append({"path": str(path), "error": f"{type(exc).__name__}: {exc}"})
|
||||
continue
|
||||
raise
|
||||
if central_failures:
|
||||
status_path = project_root / "dados" / "mcp-admin-route-acceptance-central-write-status.json"
|
||||
_write_text(
|
||||
status_path,
|
||||
json.dumps(
|
||||
{
|
||||
"generatedAt": utc_now(),
|
||||
"ok": False,
|
||||
"centralPlatformFolder": str(central_platform_folder),
|
||||
"failures": central_failures,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
),
|
||||
)
|
||||
records.append(
|
||||
GeneratedFile(
|
||||
path=str(status_path),
|
||||
description="Status de escrita central do catalogo de rotas administrativas.",
|
||||
function="mcp admin route acceptance central write status",
|
||||
file_type="json",
|
||||
changed_by="mais_humana.mcp_admin_route_acceptance",
|
||||
change_summary="Registrada falha de escrita central sem abortar artefatos do projeto real.",
|
||||
relation_to_order="0040_EXECUTIVA__materializar-escrita-central-e-sql-semantico-sem-permissionerror",
|
||||
)
|
||||
)
|
||||
return tuple(records)
|
||||
|
||||
|
||||
def run_admin_route_acceptance(
|
||||
*,
|
||||
project_root: Path,
|
||||
central_platform_folder: Path | None = None,
|
||||
platform_id: str | None = None,
|
||||
profile_id: str | None = None,
|
||||
operation: str | None = None,
|
||||
status: str | None = None,
|
||||
limit: int = 120,
|
||||
) -> tuple[AdminRouteAcceptanceReport, tuple[GeneratedFile, ...]]:
|
||||
"""Build and write route acceptance artifacts."""
|
||||
|
||||
report = build_admin_route_acceptance_report(
|
||||
platform_id=platform_id,
|
||||
profile_id=profile_id,
|
||||
operation=operation,
|
||||
status=status,
|
||||
limit=limit,
|
||||
)
|
||||
records = write_admin_route_acceptance_artifacts(report, project_root, central_platform_folder=central_platform_folder)
|
||||
report_with_files = AdminRouteAcceptanceReport(
|
||||
report_id=report.report_id,
|
||||
generated_at=report.generated_at,
|
||||
summary=report.summary,
|
||||
returned_cases=report.returned_cases,
|
||||
sample_limit=report.sample_limit,
|
||||
filters=report.filters,
|
||||
generated_files=tuple(record.path for record in records),
|
||||
)
|
||||
return report_with_files, records
|
||||
Reference in New Issue
Block a user