371 lines
20 KiB
Python
371 lines
20 KiB
Python
"""Human workflow registry for cross-platform operational maturity."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Iterable, Sequence
|
|
|
|
from .governance_models import EcosystemGovernancePortfolio, GovernanceStatus
|
|
from .models import as_plain_data, merge_unique
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class WorkflowStep:
|
|
step_id: str
|
|
title: str
|
|
owner_platform: str
|
|
required_signals: tuple[str, ...]
|
|
validation: str
|
|
human_output: str
|
|
|
|
def to_dict(self) -> dict[str, object]:
|
|
return as_plain_data(self)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class HumanWorkflow:
|
|
workflow_id: str
|
|
title: str
|
|
purpose: str
|
|
primary_profile: str
|
|
platforms: tuple[str, ...]
|
|
steps: tuple[WorkflowStep, ...]
|
|
expected_artifacts: tuple[str, ...]
|
|
risk_if_missing: str
|
|
|
|
def to_dict(self) -> dict[str, object]:
|
|
return as_plain_data(self)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class WorkflowEvaluation:
|
|
workflow_id: str
|
|
title: str
|
|
status: str
|
|
score: int
|
|
passed_steps: int
|
|
total_steps: int
|
|
blocking_steps: tuple[str, ...]
|
|
next_actions: tuple[str, ...]
|
|
evidence: tuple[str, ...]
|
|
|
|
def to_dict(self) -> dict[str, object]:
|
|
return as_plain_data(self)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class WorkflowPortfolio:
|
|
workflows: tuple[HumanWorkflow, ...]
|
|
evaluations: tuple[WorkflowEvaluation, ...]
|
|
summary: tuple[str, ...]
|
|
|
|
def to_dict(self) -> dict[str, object]:
|
|
return as_plain_data(self)
|
|
|
|
|
|
def step(
|
|
step_id: str,
|
|
title: str,
|
|
owner_platform: str,
|
|
required_signals: Iterable[str],
|
|
validation: str,
|
|
human_output: str,
|
|
) -> WorkflowStep:
|
|
return WorkflowStep(
|
|
step_id=step_id,
|
|
title=title,
|
|
owner_platform=owner_platform,
|
|
required_signals=tuple(required_signals),
|
|
validation=validation,
|
|
human_output=human_output,
|
|
)
|
|
|
|
|
|
WORKFLOWS: tuple[HumanWorkflow, ...] = (
|
|
HumanWorkflow(
|
|
workflow_id="docs-canonical-decision",
|
|
title="Decisao canonica de Docs",
|
|
purpose="Resolver ambiguidade entre catalogOnly, responseReady minimo e excecao formal.",
|
|
primary_profile="planejamento_estrategico",
|
|
platforms=("docs", "mcps", "ui", "compliance"),
|
|
steps=(
|
|
step("docs-source", "Identificar fonte documental canonica", "docs", ("docs", "canonical", "contrato"), "abrir contrato Docs", "fonte documental declarada"),
|
|
step("docs-read", "Promover leitura minima ou declarar excecao", "docs", ("responseReady", "catalogOnly", "excecao"), "executar leitura ou registrar decisao", "decisao legivel"),
|
|
step("mcp-reconcile", "Reconciliar readiness MCP", "mcps", ("readiness", "blocker", "docs"), "comparar readiness global", "blocker Docs classificado"),
|
|
step("ui-explain", "Explicar estado para painel e GPT", "ui", ("sameSource", "panelReady", "sourceHash"), "validar mesma fonte", "painel e GPT explicam igual"),
|
|
),
|
|
expected_artifacts=("contrato Docs", "readiness reconciliado", "evidencia HTTP", "OS de continuidade"),
|
|
risk_if_missing="Docs continua como blocker global ambiguo.",
|
|
),
|
|
HumanWorkflow(
|
|
workflow_id="byok-live-controlled",
|
|
title="BYOK live controlado por tenant",
|
|
purpose="Provar credencial real sem vazamento, com usuario, organizacao, entitlement, smoke e consumo.",
|
|
primary_profile="tecnico",
|
|
platforms=("identity", "business", "integracoes", "compliance", "customer_ops"),
|
|
steps=(
|
|
step("identity-org", "Criar organizacao de teste", "identity", ("organizationId", "tenant"), "criar organizacao", "tenant rastreavel"),
|
|
step("identity-user", "Criar usuario e sessao", "identity", ("actorId", "session", "role"), "assumir usuario", "ator autorizado"),
|
|
step("business-entitlement", "Vincular produto e entitlement", "business", ("entitlement", "productId", "plan"), "consultar entitlement", "uso permitido"),
|
|
step("integracoes-session", "Criar sessao BYOK", "integracoes", ("BYOK", "credentialRef"), "gerar credentialRef", "segredo nao exposto"),
|
|
step("integracoes-smoke", "Executar smoke readonly", "integracoes", ("smoke", "readonly", "usage"), "rodar smoke", "provider validado"),
|
|
step("compliance-redaction", "Validar nao vazamento", "compliance", ("redaction", "audit", "trace"), "varrer relatorios", "auditoria segura"),
|
|
step("support-diagnostic", "Criar diagnostico para falha", "customer_ops", ("diagnostic", "nextAction"), "simular falha", "suporte orientado"),
|
|
),
|
|
expected_artifacts=("credentialRef", "auditId", "usage", "redaction check", "diagnostico"),
|
|
risk_if_missing="Integracao parece pronta, mas nao e autosservico real.",
|
|
),
|
|
HumanWorkflow(
|
|
workflow_id="business-commercial-gate",
|
|
title="Gate comercial unico por Business",
|
|
purpose="Garantir que plano, franquia, consumo e bloqueio venham de Business.",
|
|
primary_profile="financeiro",
|
|
platforms=("business", "finance", "integracoes", "public", "customer_ops"),
|
|
steps=(
|
|
step("plan-source", "Definir plano como fonte Business", "business", ("plano", "entitlement"), "consultar plano", "plano unico"),
|
|
step("quota-source", "Materializar franquia e excedente", "business", ("quota", "usage", "franquia"), "simular consumo", "limite claro"),
|
|
step("billing-link", "Reconciliar cobranca Finance", "finance", ("invoice", "reconciliation"), "gerar extrato", "fatura rastreavel"),
|
|
step("product-isolation", "Isolar blocker por produto", "business", ("productId", "blockerId"), "listar blockers", "impacto isolado"),
|
|
step("support-message", "Gerar mensagem humana de bloqueio", "customer_ops", ("support", "nextAction"), "validar mensagem", "cliente orientado"),
|
|
),
|
|
expected_artifacts=("entitlement", "invoice", "usage", "blocker matrix", "mensagem de suporte"),
|
|
risk_if_missing="Produto pode ser vendido ou bloqueado por regra divergente.",
|
|
),
|
|
HumanWorkflow(
|
|
workflow_id="mcp-panel-same-source",
|
|
title="Painel humano com mesma fonte do GPT",
|
|
purpose="Impedir divergencia entre UI, MCP e explicacao do GPT.",
|
|
primary_profile="administrador_empresa",
|
|
platforms=("mcps", "ui", "docs", "identity", "business"),
|
|
steps=(
|
|
step("screen-instance", "Criar instancia administrativa", "mcps", ("viewInstance", "screenData"), "criar instancia", "payload rastreavel"),
|
|
step("source-hashes", "Gerar hashes de fonte e registros", "mcps", ("sourcePayloadHash", "sourceRecordsHash"), "comparar hashes", "mesma fonte"),
|
|
step("ui-render", "Renderizar UI sem backend paralelo", "ui", ("panelReady", "sameSource"), "validar tela", "painel confiavel"),
|
|
step("gpt-explain", "Explicar a mesma instancia pelo GPT", "mcps", ("gptExplainable", "sameSource"), "pedir explicacao", "resposta coerente"),
|
|
step("docs-contract", "Publicar contrato da tela", "docs", ("contractVersion", "schemaVersion"), "exportar contrato", "documentacao viva"),
|
|
),
|
|
expected_artifacts=("viewInstance", "source hashes", "screen contract", "evidencia HTTP"),
|
|
risk_if_missing="Painel e GPT podem mostrar verdades diferentes.",
|
|
),
|
|
HumanWorkflow(
|
|
workflow_id="identity-rbac-denial",
|
|
title="Identity com matriz RBAC de negacao",
|
|
purpose="Provar permissoes permitidas e negadas por papel, organizacao e escopo.",
|
|
primary_profile="juridico",
|
|
platforms=("identity", "compliance", "mcps", "customer_ops"),
|
|
steps=(
|
|
step("role-matrix", "Publicar matriz de papeis", "identity", ("role", "scope", "permission"), "listar papeis", "papel claro"),
|
|
step("allow-case", "Testar caminho permitido", "identity", ("allow", "200"), "executar allow", "acao liberada"),
|
|
step("deny-case", "Testar caminho negado", "identity", ("deny", "403", "forbidden"), "executar deny", "acao bloqueada"),
|
|
step("audit-case", "Registrar auditId da decisao", "compliance", ("auditId", "traceId"), "consultar audit", "evidencia juridica"),
|
|
step("support-case", "Explicar negacao para suporte", "customer_ops", ("diagnostic", "nextAction"), "gerar diagnostico", "suporte seguro"),
|
|
),
|
|
expected_artifacts=("matriz RBAC", "allow evidence", "deny evidence", "auditId", "diagnostico"),
|
|
risk_if_missing="Controle de acesso fica baseado em caminho feliz e nao em governanca real.",
|
|
),
|
|
HumanWorkflow(
|
|
workflow_id="compliance-evidence-chain",
|
|
title="Cadeia de evidencia Compliance",
|
|
purpose="Ligar politica, consentimento, redaction, audit e retencao.",
|
|
primary_profile="juridico",
|
|
platforms=("compliance", "identity", "docs", "mcps"),
|
|
steps=(
|
|
step("policy", "Publicar politica aplicavel", "compliance", ("policy", "retention"), "validar politica", "regra clara"),
|
|
step("consent", "Registrar consentimento quando aplicavel", "compliance", ("consent", "actorId"), "consultar consentimento", "consentimento auditavel"),
|
|
step("redaction", "Aplicar redaction em campos sensiveis", "compliance", ("redaction", "masked"), "rodar redaction", "sem segredo"),
|
|
step("audit", "Gerar auditId e evidenceId", "compliance", ("auditId", "evidenceId"), "consultar evidencia", "cadeia de custodia"),
|
|
step("docs", "Documentar contrato de privacidade", "docs", ("contrato", "hash"), "validar docs", "memoria institucional"),
|
|
),
|
|
expected_artifacts=("policy", "consent record", "redaction report", "audit evidence", "docs hash"),
|
|
risk_if_missing="A plataforma nao prova governanca juridica de longo prazo.",
|
|
),
|
|
HumanWorkflow(
|
|
workflow_id="customer-ops-incident",
|
|
title="Incidente Customer Ops completo",
|
|
purpose="Abrir, diagnosticar, encaminhar, resolver e auditar incidente.",
|
|
primary_profile="suporte",
|
|
platforms=("customer_ops", "identity", "business", "integracoes", "compliance"),
|
|
steps=(
|
|
step("open", "Abrir incidente", "customer_ops", ("incident", "open"), "criar incidente", "ticket criado"),
|
|
step("classify", "Classificar origem e severidade", "customer_ops", ("severity", "domain"), "classificar", "prioridade clara"),
|
|
step("handoff", "Encaminhar para owner", "customer_ops", ("handoff", "owner"), "encaminhar", "responsavel definido"),
|
|
step("resolve", "Resolver com evidencia", "customer_ops", ("resolved", "evidenceId"), "fechar incidente", "solucao rastreavel"),
|
|
step("audit", "Auditar ciclo", "compliance", ("audit", "trace"), "consultar audit", "cadeia completa"),
|
|
),
|
|
expected_artifacts=("ticket", "classification", "handoff", "resolution", "audit trail"),
|
|
risk_if_missing="Suporte perde historico e proxima acao em falhas recorrentes.",
|
|
),
|
|
HumanWorkflow(
|
|
workflow_id="cloudflare-wrangler-operations",
|
|
title="Operacao Cloudflare por wrangler",
|
|
purpose="Validar runtime real por wrangler, tratando plugin como teste esperado.",
|
|
primary_profile="tecnico",
|
|
platforms=("integracoes", "mcps", "ui", "public", "gettys"),
|
|
steps=(
|
|
step("plugin-test", "Registrar tentativa do plugin", "integracoes", ("plugin Cloudflare", "expected"), "registrar tentativa", "premissa cumprida"),
|
|
step("wrangler-auth", "Validar autenticacao wrangler quando houver trabalho real", "integracoes", ("wrangler", "whoami"), "wrangler whoami", "identidade operacional"),
|
|
step("bindings", "Verificar bindings e secrets", "integracoes", ("bindings", "secrets"), "wrangler secret/list", "runtime configurado"),
|
|
step("routes", "Validar rotas e deploy", "integracoes", ("routes", "deploy"), "wrangler deploy/check", "rota viva"),
|
|
step("logs", "Coletar logs/health", "integracoes", ("tail", "health"), "wrangler tail/health", "diagnostico real"),
|
|
),
|
|
expected_artifacts=("plugin attempt", "wrangler status", "bindings", "routes", "health evidence"),
|
|
risk_if_missing="Cloudflare fica dependente de plugin experimental ou sem prova live.",
|
|
),
|
|
HumanWorkflow(
|
|
workflow_id="intelligence-promotion",
|
|
title="Promocao controlada de Intelligence",
|
|
purpose="Tirar Intelligence de unsupported/catalogOnly apenas com endpoint, smoke e contrato.",
|
|
primary_profile="ceo",
|
|
platforms=("intelligence", "mcps", "docs", "ui"),
|
|
steps=(
|
|
step("planned-state", "Declarar estado planejado", "intelligence", ("planned", "catalogOnly"), "registrar estado", "sem ambiguidade"),
|
|
step("endpoint", "Publicar endpoint minimo", "intelligence", ("health", "profile", "readiness"), "chamar endpoint", "runtime basico"),
|
|
step("contract", "Publicar contrato", "intelligence", ("openapi", "schema"), "validar contrato", "surface auditavel"),
|
|
step("mcp-register", "Registrar no MCP", "mcps", ("provider", "readiness"), "comparar catalogo", "control-plane ciente"),
|
|
step("ui-readiness", "Expor status no painel", "ui", ("panelReady", "sameSource"), "validar tela", "status humano"),
|
|
),
|
|
expected_artifacts=("state decision", "health smoke", "openapi", "mcp readiness", "panel status"),
|
|
risk_if_missing="Intelligence permanece meio provider e confunde readiness global.",
|
|
),
|
|
HumanWorkflow(
|
|
workflow_id="release-and-rollback",
|
|
title="Release com rollback e contrato",
|
|
purpose="Promover mudanca sem quebrar contrato, UI, GPT ou auditoria.",
|
|
primary_profile="gestor_operacional",
|
|
platforms=("platform_base", "mcps", "docs", "ui"),
|
|
steps=(
|
|
step("version", "Gerar contractVersion/schemaVersion", "platform_base", ("contractVersion", "schemaVersion"), "exportar versao", "versao clara"),
|
|
step("compat", "Declarar compatibilidade", "platform_base", ("compatibilityVersion", "breakingChanges"), "validar compat", "risco explicito"),
|
|
step("smoke", "Executar smoke regressivo", "mcps", ("smoke", "readiness"), "rodar smoke", "prova tecnica"),
|
|
step("rollback", "Registrar rollback", "platform_base", ("rollback", "previousVersion"), "validar rollback", "reversao possivel"),
|
|
step("docs", "Publicar changelog", "docs", ("changelog", "migrationNotes"), "validar docs", "memoria de mudanca"),
|
|
),
|
|
expected_artifacts=("version", "compatibility", "smoke report", "rollback plan", "changelog"),
|
|
risk_if_missing="Mudancas futuras quebram contratos sem trilha de reversao.",
|
|
),
|
|
)
|
|
|
|
|
|
def signal_text_for_card(portfolio: EcosystemGovernancePortfolio, platform_id: str) -> str:
|
|
card = portfolio.card_for(platform_id)
|
|
if card is None:
|
|
return ""
|
|
parts: list[str] = [card.platform_id, card.status_label, card.maturity.value]
|
|
for check in card.checks:
|
|
parts.extend([check.check_id, check.title, check.reason, check.next_action, check.status.value])
|
|
for evidence in check.evidence:
|
|
parts.append(evidence.path)
|
|
parts.append(evidence.summary)
|
|
return "\n".join(parts).lower()
|
|
|
|
|
|
def evaluate_step(step_item: WorkflowStep, portfolio: EcosystemGovernancePortfolio) -> tuple[bool, tuple[str, ...]]:
|
|
text = signal_text_for_card(portfolio, step_item.owner_platform)
|
|
missing = tuple(signal for signal in step_item.required_signals if signal.lower() not in text)
|
|
return (not missing, missing)
|
|
|
|
|
|
def evaluate_workflow(workflow: HumanWorkflow, portfolio: EcosystemGovernancePortfolio) -> WorkflowEvaluation:
|
|
passed = 0
|
|
blocking: list[str] = []
|
|
actions: list[str] = []
|
|
evidence: list[str] = []
|
|
for step_item in workflow.steps:
|
|
ok, missing = evaluate_step(step_item, portfolio)
|
|
card = portfolio.card_for(step_item.owner_platform)
|
|
if ok:
|
|
passed += 1
|
|
evidence.append(f"{step_item.step_id}: {step_item.human_output}")
|
|
else:
|
|
blocking.append(f"{step_item.step_id}: faltam {', '.join(missing[:4])}")
|
|
if card and card.next_actions:
|
|
actions.append(card.next_actions[0])
|
|
else:
|
|
actions.append(step_item.validation)
|
|
total = len(workflow.steps)
|
|
score = round((passed / total) * 100) if total else 0
|
|
status = "pronto" if score >= 85 else "util" if score >= 65 else "atencao" if score >= 40 else "bloqueado"
|
|
return WorkflowEvaluation(
|
|
workflow_id=workflow.workflow_id,
|
|
title=workflow.title,
|
|
status=status,
|
|
score=score,
|
|
passed_steps=passed,
|
|
total_steps=total,
|
|
blocking_steps=tuple(blocking),
|
|
next_actions=merge_unique(actions)[:8],
|
|
evidence=tuple(evidence),
|
|
)
|
|
|
|
|
|
def build_workflow_portfolio(portfolio: EcosystemGovernancePortfolio, workflows: Sequence[HumanWorkflow] = WORKFLOWS) -> WorkflowPortfolio:
|
|
evaluations = tuple(evaluate_workflow(workflow, portfolio) for workflow in workflows)
|
|
ready = sum(1 for item in evaluations if item.status in {"pronto", "util"})
|
|
blocked = sum(1 for item in evaluations if item.status == "bloqueado")
|
|
avg = round(sum(item.score for item in evaluations) / len(evaluations)) if evaluations else 0
|
|
summary = (
|
|
f"Workflows avaliados: {len(evaluations)}",
|
|
f"Workflows prontos/uteis: {ready}",
|
|
f"Workflows bloqueados: {blocked}",
|
|
f"Score medio de workflow: {avg}",
|
|
)
|
|
return WorkflowPortfolio(workflows=tuple(workflows), evaluations=evaluations, summary=summary)
|
|
|
|
|
|
def workflow_markdown(portfolio: WorkflowPortfolio) -> str:
|
|
lines = ["# Workflows humanos operacionais", ""]
|
|
lines.extend(f"- {item}" for item in portfolio.summary)
|
|
lines.append("")
|
|
by_id = {workflow.workflow_id: workflow for workflow in portfolio.workflows}
|
|
for evaluation in sorted(portfolio.evaluations, key=lambda item: (item.score, item.workflow_id)):
|
|
workflow = by_id[evaluation.workflow_id]
|
|
lines.append(f"## {workflow.title}")
|
|
lines.append("")
|
|
lines.append(f"- workflow_id: `{workflow.workflow_id}`")
|
|
lines.append(f"- perfil principal: `{workflow.primary_profile}`")
|
|
lines.append(f"- status: `{evaluation.status}`")
|
|
lines.append(f"- score: `{evaluation.score}`")
|
|
lines.append(f"- passos: `{evaluation.passed_steps}/{evaluation.total_steps}`")
|
|
lines.append(f"- risco se faltar: {workflow.risk_if_missing}")
|
|
if evaluation.blocking_steps:
|
|
lines.append("- bloqueios:")
|
|
for item in evaluation.blocking_steps:
|
|
lines.append(f" - {item}")
|
|
if evaluation.next_actions:
|
|
lines.append("- proximas acoes:")
|
|
for item in evaluation.next_actions:
|
|
lines.append(f" - {item}")
|
|
lines.append("")
|
|
lines.append("Passos:")
|
|
for step_item in workflow.steps:
|
|
lines.append(f"- `{step_item.step_id}` {step_item.title} ({step_item.owner_platform}) -> {step_item.human_output}")
|
|
lines.append("")
|
|
return "\n".join(lines).strip() + "\n"
|
|
|
|
|
|
def workflow_rows(portfolio: WorkflowPortfolio) -> list[list[str]]:
|
|
rows = [["workflow_id", "status", "score", "passed_steps", "total_steps", "blocking_steps", "next_actions"]]
|
|
for evaluation in sorted(portfolio.evaluations, key=lambda item: item.workflow_id):
|
|
rows.append(
|
|
[
|
|
evaluation.workflow_id,
|
|
evaluation.status,
|
|
str(evaluation.score),
|
|
str(evaluation.passed_steps),
|
|
str(evaluation.total_steps),
|
|
" | ".join(evaluation.blocking_steps),
|
|
" | ".join(evaluation.next_actions),
|
|
]
|
|
)
|
|
return rows
|
|
|
|
|
|
def workflow_action_items(portfolio: WorkflowPortfolio, limit: int = 25) -> tuple[str, ...]:
|
|
actions: list[str] = []
|
|
for evaluation in sorted(portfolio.evaluations, key=lambda item: (item.score, item.workflow_id)):
|
|
for action in evaluation.next_actions:
|
|
actions.append(f"{evaluation.workflow_id}: {action}")
|
|
if len(actions) >= limit:
|
|
return merge_unique(actions)
|
|
return merge_unique(actions)
|