feat: fundar plataforma mais humana

This commit is contained in:
Ami Soares
2026-04-30 06:42:00 -03:00
commit c9c1056193
183 changed files with 639629 additions and 0 deletions

View File

@@ -0,0 +1,835 @@
"""Signal extraction rules for the Mais Humana operational dossier.
This module turns local repository evidence into normalized operational signals.
It does not decide whether a project is good or bad by a single keyword. The
goal is to preserve useful nuance:
* a Docs catalog-only decision can be a formal exception or a blocker;
* a BYOK credential reference is a capability, while a missing tenant smoke is
a blocker;
* Cloudflare plugin denial is expected and must not become a platform blocker;
* wrangler, HTTP evidence, readiness, sameSource, and panelReady are real
operational signals;
* repository, Git, tests, OpenAPI, and security redaction remain separate gates.
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, Sequence
from .models import EvidenceKind, NeedCategory, PlatformHumanReport, PlatformScan, Recommendation, merge_unique, slugify
from .operational_models import (
EvidenceRole,
GateDomain,
HumanReadinessStage,
OperationalSignal,
SignalKind,
SignalSeverity,
SourceConfidence,
SourceReference,
source_refs_from_evidence,
source_refs_from_strings,
stable_digest,
)
@dataclass(slots=True)
class SignalRule:
"""A lightweight matching rule for evidence summaries, warning text, and paths."""
rule_id: str
title: str
kind: SignalKind
domain: GateDomain
severity: SignalSeverity
stage: HumanReadinessStage
categories: tuple[NeedCategory, ...]
patterns: tuple[str, ...]
positive_summary: str
next_action: str
tags: tuple[str, ...] = ()
def matches(self, text: str) -> bool:
lowered = text.lower()
return any(re.search(pattern, lowered, re.I) for pattern in self.patterns)
def rule(
rule_id: str,
title: str,
kind: SignalKind,
domain: GateDomain,
severity: SignalSeverity,
stage: HumanReadinessStage,
categories: Iterable[NeedCategory],
patterns: Iterable[str],
summary: str,
next_action: str,
tags: Iterable[str] = (),
) -> SignalRule:
return SignalRule(
rule_id=rule_id,
title=title,
kind=kind,
domain=domain,
severity=severity,
stage=stage,
categories=tuple(categories),
patterns=tuple(patterns),
positive_summary=summary,
next_action=next_action,
tags=tuple(tags),
)
CAPABILITY_RULES: tuple[SignalRule, ...] = (
rule(
"readiness-surface",
"Readiness operacional detectada",
SignalKind.CAPABILITY,
GateDomain.OBSERVABILITY,
SignalSeverity.INFO,
HumanReadinessStage.TECHNICAL_READY,
(NeedCategory.OBSERVABILITY, NeedCategory.OPERATIONS),
(r"\breadiness\b", r"\bready\b", r"prontid"),
"O repositorio possui indicios de readiness ou prontidao operacional.",
"manter readiness como evidencia regressiva",
("readiness",),
),
rule(
"health-surface",
"Health check detectado",
SignalKind.CAPABILITY,
GateDomain.RUNTIME,
SignalSeverity.INFO,
HumanReadinessStage.TECHNICAL_READY,
(NeedCategory.OPERATIONS, NeedCategory.OBSERVABILITY),
(r"\bhealth\b", r"/health\b"),
"O repositorio expoe ou documenta health check.",
"validar health em smoke local ou publicado",
("health",),
),
rule(
"openapi-contract",
"Contrato OpenAPI detectado",
SignalKind.CAPABILITY,
GateDomain.CONTRACT,
SignalSeverity.INFO,
HumanReadinessStage.HUMAN_EXPLAINABLE,
(NeedCategory.DOCUMENTATION, NeedCategory.INTEGRATION, NeedCategory.GOVERNANCE),
(r"openapi", r"swagger"),
"O repositorio possui contrato OpenAPI, documento ou rota relacionada.",
"manter contrato sincronizado com rotas reais",
("openapi",),
),
rule(
"panel-ready-signal",
"panelReady detectado",
SignalKind.CAPABILITY,
GateDomain.PANEL,
SignalSeverity.INFO,
HumanReadinessStage.PANEL_READY,
(NeedCategory.EXPERIENCE, NeedCategory.GOVERNANCE),
(r"panelready", r"panel ready", r"painel.*pront"),
"Ha indicio de contrato de tela pronto para painel humano.",
"validar se panelReady usa a mesma fonte do GPT",
("panelReady",),
),
rule(
"same-source-signal",
"sameSource detectado",
SignalKind.CAPABILITY,
GateDomain.PANEL,
SignalSeverity.INFO,
HumanReadinessStage.PANEL_READY,
(NeedCategory.EXPERIENCE, NeedCategory.GOVERNANCE, NeedCategory.OBSERVABILITY),
(r"samesource", r"same source", r"mesma fonte", r"sourcehash", r"recordsHash"),
"Ha indicio de mesma fonte entre GPT, painel e evidencia.",
"manter hash de fonte e registros em regressao",
("sameSource",),
),
rule(
"credential-ref-signal",
"credentialRef detectado",
SignalKind.CAPABILITY,
GateDomain.SECURITY,
SignalSeverity.INFO,
HumanReadinessStage.CONTROLLED_READY,
(NeedCategory.SECURITY, NeedCategory.INTEGRATION),
(r"credentialref", r"credential ref", r"credential:"),
"A plataforma usa referencia de credencial em vez de expor segredo bruto.",
"validar nao vazamento em relatorios, logs e respostas",
("credentialRef", "redaction"),
),
rule(
"byok-signal",
"BYOK detectado",
SignalKind.CAPABILITY,
GateDomain.INTEGRATION,
SignalSeverity.INFO,
HumanReadinessStage.CONTROLLED_READY,
(NeedCategory.INTEGRATION, NeedCategory.SECURITY, NeedCategory.COMMERCIAL),
(r"\bbyok\b", r"bring your own key", r"credencial.*cliente"),
"A jornada BYOK aparece como superficie tecnica ou comercial.",
"validar cadeia organizacao, usuario, entitlement, credentialRef, smoke e consumo",
("BYOK",),
),
rule(
"audit-trace-signal",
"Trace e auditoria detectados",
SignalKind.CAPABILITY,
GateDomain.OBSERVABILITY,
SignalSeverity.INFO,
HumanReadinessStage.HUMAN_EXPLAINABLE,
(NeedCategory.OBSERVABILITY, NeedCategory.GOVERNANCE),
(r"\baudit\b", r"\btrace\b", r"auditid", r"traceid", r"auditoria"),
"A plataforma registra ou expõe trace/audit para leitura operacional.",
"garantir que trace/audit nao contenham segredo bruto",
("audit", "trace"),
),
rule(
"business-entitlement-signal",
"Entitlement ou regra comercial detectada",
SignalKind.CAPABILITY,
GateDomain.BUSINESS,
SignalSeverity.INFO,
HumanReadinessStage.HUMAN_EXPLAINABLE,
(NeedCategory.COMMERCIAL, NeedCategory.FINANCE),
(r"entitlement", r"checkout", r"invoice", r"billing", r"franquia", r"cobranca"),
"Ha evidencia de regra comercial, cobranca, consumo ou entitlement.",
"sincronizar Business como fonte unica de plano, franquia e bloqueio",
("business", "entitlement"),
),
rule(
"identity-rbac-signal",
"Identity/RBAC detectado",
SignalKind.CAPABILITY,
GateDomain.IDENTITY,
SignalSeverity.INFO,
HumanReadinessStage.HUMAN_EXPLAINABLE,
(NeedCategory.SECURITY, NeedCategory.ADMINISTRATION, NeedCategory.GOVERNANCE),
(r"\brbac\b", r"identity", r"organizacao", r"organization", r"user", r"usuario", r"tenant"),
"Ha evidencia de identidade, papel, organizacao, tenant ou permissao.",
"amarrar actor, organizationId, role e escopo nos contratos humanos",
("identity", "rbac"),
),
rule(
"wrangler-operational-signal",
"Wrangler operacional detectado",
SignalKind.CAPABILITY,
GateDomain.CLOUD,
SignalSeverity.INFO,
HumanReadinessStage.TECHNICAL_READY,
(NeedCategory.OPERATIONS, NeedCategory.INTEGRATION),
(r"\bwrangler\b", r"workers\.dev", r"cloudflare worker"),
"Ha evidencia de operacao Cloudflare por wrangler/Worker.",
"usar wrangler para deploy, logs, rotas, secrets e health checks",
("wrangler", "cloudflare"),
),
)
BLOCKER_RULES: tuple[SignalRule, ...] = (
rule(
"repo-missing",
"Repositorio real ausente",
SignalKind.BLOCKER,
GateDomain.REPOSITORY,
SignalSeverity.CRITICAL,
HumanReadinessStage.NOT_FOUND,
(NeedCategory.GOVERNANCE, NeedCategory.OPERATIONS),
(r"repositorio real nao encontrado", r"repo.*ausente", r"not found"),
"Sem repositorio real local nao ha base material para validar a plataforma.",
"criar ou clonar repositorio real sem numero da pasta gerencial",
("repository",),
),
rule(
"git-missing",
"Git local ausente ou inacessivel",
SignalKind.BLOCKER,
GateDomain.REPOSITORY,
SignalSeverity.HIGH,
HumanReadinessStage.LOCAL_ONLY,
(NeedCategory.GOVERNANCE, NeedCategory.OPERATIONS),
(r"sem \.git", r"git.*ausente", r"git.*inacess", r"permission denied.*\.git", r"index\.lock"),
"Sem Git operacional a rodada nao consegue registrar commit, hash e sincronizacao.",
"resolver ACL de .git, configurar origin e repetir commit/push",
("git", "sync"),
),
rule(
"tests-missing",
"Testes nao encontrados",
SignalKind.GAP,
GateDomain.TESTS,
SignalSeverity.MEDIUM,
HumanReadinessStage.TECHNICAL_READY,
(NeedCategory.OPERATIONS, NeedCategory.OBSERVABILITY),
(r"testes nao encontrados", r"no tests", r"sem teste"),
"A varredura nao encontrou suite ou smoke detectavel.",
"criar teste canonico de health/readiness/contrato humano",
("tests",),
),
rule(
"openapi-missing",
"Contrato OpenAPI nao encontrado",
SignalKind.GAP,
GateDomain.CONTRACT,
SignalSeverity.MEDIUM,
HumanReadinessStage.TECHNICAL_READY,
(NeedCategory.DOCUMENTATION, NeedCategory.INTEGRATION),
(r"openapi nao encontrado", r"openapi.*missing", r"sem openapi"),
"Sem contrato OpenAPI ou equivalente, a integracao fica menos auditavel.",
"publicar OpenAPI minima ou declarar contrato alternativo versionado",
("openapi", "contract"),
),
rule(
"docs-catalog-only",
"Docs catalogOnly exige decisao formal",
SignalKind.BLOCKER,
GateDomain.DOCS,
SignalSeverity.HIGH,
HumanReadinessStage.CATALOG_ONLY,
(NeedCategory.DOCUMENTATION, NeedCategory.GOVERNANCE),
(r"catalogonly", r"catalog_only", r"catalog-only", r"docs.*catalog"),
"Docs aparece como catalogOnly; isso precisa ser excecao formal ou leitura minima responseReady.",
"promover leitura canonica minima de Docs ou registrar excecao deliberada",
("docs", "catalogOnly"),
),
rule(
"intelligence-unsupported",
"Intelligence sem promocao operacional completa",
SignalKind.BLOCKER,
GateDomain.GOVERNANCE,
SignalSeverity.HIGH,
HumanReadinessStage.CATALOG_ONLY,
(NeedCategory.STRATEGY, NeedCategory.OBSERVABILITY),
(r"unsupported", r"catalogonly-local-ready", r"intelligence.*planned", r"public endpoint.*missing"),
"Intelligence aparece local/catalogada, mas ainda depende de endpoint, storage ou registro operacional.",
"manter como catalogOnly planejada ate publicar smoke HTTP e registrar no MCP central",
("intelligence", "unsupported"),
),
rule(
"credential-live-pending",
"Credencial live ou BYOK pendente",
SignalKind.BLOCKER,
GateDomain.INTEGRATION,
SignalSeverity.HIGH,
HumanReadinessStage.CONTROLLED_READY,
(NeedCategory.INTEGRATION, NeedCategory.SECURITY, NeedCategory.COMMERCIAL),
(r"token.*missing", r"credential.*not.*ready", r"needs_token", r"live.*credential", r"credencial live", r"byok.*pend"),
"A integracao depende de credencial live, token ou credentialRef por tenant.",
"criar sessao BYOK, gerar credentialRef, executar smoke readonly e provar nao vazamento",
("credential", "BYOK"),
),
rule(
"panel-source-divergence",
"Painel e GPT podem divergir",
SignalKind.RISK,
GateDomain.PANEL,
SignalSeverity.HIGH,
HumanReadinessStage.PANEL_READY,
(NeedCategory.EXPERIENCE, NeedCategory.GOVERNANCE),
(r"samesource.*false", r"same source.*false", r"diverg", r"source.*mismatch"),
"Ha indicio de divergencia entre fonte do painel e fonte explicada pelo GPT.",
"reconciliar sourceEndpoint, sourceToolId, sourcePayloadHash e sourceRecordsHash",
("sameSource", "panelReady"),
),
rule(
"plugin-cloudflare-expected-denial",
"Negativa do plugin Cloudflare nao e blocker operacional",
SignalKind.EXCEPTION,
GateDomain.CLOUD,
SignalSeverity.INFO,
HumanReadinessStage.TECHNICAL_READY,
(NeedCategory.OPERATIONS, NeedCategory.INTEGRATION),
(r"plugin.*cloudflare.*denied", r"cloudflare-plugin-auth-denied", r"user rejected mcp tool call"),
"Falha ou negativa do plugin Cloudflare e esperada e nao deve bloquear a OS.",
"registrar tentativa do plugin e seguir trabalho operacional por wrangler quando aplicavel",
("cloudflare-plugin", "expected"),
),
rule(
"cloudflare-binding-local-blocker",
"Bindings Cloudflare ausentes no ambiente local",
SignalKind.RISK,
GateDomain.CLOUD,
SignalSeverity.MEDIUM,
HumanReadinessStage.TECHNICAL_READY,
(NeedCategory.OPERATIONS, NeedCategory.INTEGRATION),
(r"cloudflare-bindings", r"binding.*missing", r"bindings.*ausent", r"d1.*missing", r"kv.*missing", r"r2.*missing"),
"O runtime local indica bindings ausentes; isso limita prova live, mas nao invalida evidencia local.",
"validar bindings com wrangler e registrar ambiente alvo do smoke",
("cloudflare", "bindings"),
),
)
PLATFORM_SPECIFIC_SIGNALS: dict[str, tuple[SignalRule, ...]] = {
"docs": (
rule(
"docs-canonical-read",
"Leitura canonica de Docs precisa ficar explicita",
SignalKind.DECISION,
GateDomain.DOCS,
SignalSeverity.HIGH,
HumanReadinessStage.CATALOG_ONLY,
(NeedCategory.DOCUMENTATION, NeedCategory.GOVERNANCE),
(r"docs", r"document", r"contrato", r"canon"),
"Docs precisa decidir entre leitura responseReady minima e excecao catalogOnly formal.",
"criar gate Docs: responseReady minimo ou excecao documentada sem bloquear ready global",
("docs", "decision"),
),
),
"integracoes": (
rule(
"integracoes-byok-chain",
"Jornada BYOK ponta a ponta deve ser provada",
SignalKind.DECISION,
GateDomain.INTEGRATION,
SignalSeverity.HIGH,
HumanReadinessStage.CONTROLLED_READY,
(NeedCategory.INTEGRATION, NeedCategory.SECURITY, NeedCategory.COMMERCIAL),
(r"byok", r"credentialref", r"cloudflare", r"gitlab", r"stripe", r"whatsapp"),
"Integracoes tem base BYOK, mas precisa provar usuario, organizacao, entitlement, credentialRef e smoke.",
"executar fluxo encadeado BYOK com nao vazamento e consumo auditavel",
("BYOK", "integracoes"),
),
),
"business": (
rule(
"business-blocker-isolation",
"Business deve isolar blockers por produto",
SignalKind.DECISION,
GateDomain.BUSINESS,
SignalSeverity.MEDIUM,
HumanReadinessStage.HUMAN_EXPLAINABLE,
(NeedCategory.COMMERCIAL, NeedCategory.FINANCE, NeedCategory.GOVERNANCE),
(r"blocker", r"panelready", r"entitlement", r"readycontrolled", r"commercial"),
"Business aparece como fonte de readiness comercial e precisa impedir contaminacao global indevida.",
"validar blocker por productId, stage e impacto comercial isolado",
("business", "blocker-policy"),
),
),
"compliance": (
rule(
"compliance-admin-view",
"Compliance deve manter admin view same-source",
SignalKind.DECISION,
GateDomain.COMPLIANCE,
SignalSeverity.MEDIUM,
HumanReadinessStage.PANEL_READY,
(NeedCategory.LEGAL, NeedCategory.SECURITY, NeedCategory.GOVERNANCE),
(r"compliance\.admin_view\.readiness", r"sameSource", r"panelReady", r"retention", r"policy"),
"Compliance possui admin view e deve manter mesma fonte, redaction, retention e evidencia.",
"validar regressao de panelReady, source hash, retention e dados redigidos",
("compliance", "admin-view"),
),
),
"intelligence": (
rule(
"intelligence-promotion-gates",
"Intelligence precisa de gates de promocao",
SignalKind.DECISION,
GateDomain.GOVERNANCE,
SignalSeverity.HIGH,
HumanReadinessStage.CATALOG_ONLY,
(NeedCategory.STRATEGY, NeedCategory.OBSERVABILITY, NeedCategory.GOVERNANCE),
(r"runtimeMinimum", r"responseReadyControlled", r"catalogOnly-local-ready", r"public smoke"),
"Intelligence ja descreve gates, mas precisa evidencia publica para sair de catalogOnly local.",
"executar smoke publico health/profile/readiness/openapi/admin e publicar evidencia",
("intelligence", "promotion"),
),
),
}
def evidence_text(report: PlatformHumanReport) -> str:
parts = [
report.platform.platform_id,
report.platform.title,
report.platform.mission,
report.scan.readme_excerpt,
" ".join(report.scan.warnings),
]
parts.extend(evidence.summary for evidence in report.scan.evidence[:240])
parts.extend(evidence.path for evidence in report.scan.evidence[:240])
parts.extend(recommendation.title + " " + recommendation.reason for recommendation in report.recommendations[:12])
return "\n".join(parts)
def refs_for_rule(report: PlatformHumanReport, rule_item: SignalRule, limit: int = 6) -> tuple[SourceReference, ...]:
matched = []
for evidence in report.scan.evidence:
text = f"{evidence.path} {evidence.summary} {' '.join(evidence.tags)}"
if rule_item.matches(text):
matched.append(evidence)
refs = list(source_refs_from_evidence(matched, limit=limit))
if not refs and report.scan.warnings:
for warning in report.scan.warnings:
if rule_item.matches(warning):
refs.append(SourceReference(path=report.scan.repo_path, summary=warning, confidence=SourceConfidence.DERIVED, role=EvidenceRole.PRIMARY))
if not refs and report.platform.known_blockers:
for blocker in report.platform.known_blockers:
if rule_item.matches(blocker) or rule_item.kind == SignalKind.BLOCKER:
refs.append(SourceReference(path=report.scan.repo_path, summary=blocker, confidence=SourceConfidence.DECLARED, role=EvidenceRole.PRIMARY))
if not refs and rule_item.matches(report.scan.readme_excerpt):
refs.append(SourceReference(path=f"{report.scan.repo_path}/README.md", summary="README contem sinal relacionado.", confidence=SourceConfidence.DERIVED))
return tuple(refs[:limit])
def signal_from_rule(report: PlatformHumanReport, rule_item: SignalRule, refs: Sequence[SourceReference] | None = None) -> OperationalSignal:
refs = tuple(refs or ())
base = {
"platform": report.platform.platform_id,
"rule": rule_item.rule_id,
"refs": [ref.reference for ref in refs],
}
return OperationalSignal(
signal_id=f"{report.platform.platform_id}.{rule_item.rule_id}.{stable_digest(base, length=8)}",
platform_id=report.platform.platform_id,
kind=rule_item.kind,
domain=rule_item.domain,
title=rule_item.title,
summary=rule_item.positive_summary,
severity=rule_item.severity,
stage=rule_item.stage,
categories=rule_item.categories,
sources=tuple(refs),
tags=rule_item.tags,
next_action=rule_item.next_action,
)
def scan_rules(report: PlatformHumanReport, rules: Sequence[SignalRule]) -> tuple[OperationalSignal, ...]:
text = evidence_text(report)
signals: list[OperationalSignal] = []
for rule_item in rules:
if not rule_item.matches(text):
continue
refs = refs_for_rule(report, rule_item)
signals.append(signal_from_rule(report, rule_item, refs))
return tuple(signals)
def repository_signals(report: PlatformHumanReport) -> tuple[OperationalSignal, ...]:
scan = report.scan
signals: list[OperationalSignal] = []
if scan.exists:
signals.append(
OperationalSignal(
signal_id=f"{scan.platform.platform_id}.repo.exists",
platform_id=scan.platform.platform_id,
kind=SignalKind.CAPABILITY,
domain=GateDomain.REPOSITORY,
title="Repositorio local encontrado",
summary="O espelho local existe e pode ser analisado.",
severity=SignalSeverity.INFO,
stage=HumanReadinessStage.LOCAL_ONLY,
sources=(SourceReference(path=scan.repo_path, summary="Repositorio existe no filesystem.", confidence=SourceConfidence.DIRECT),),
tags=("repository",),
next_action="manter repositorio sincronizado e com hash rastreavel",
)
)
else:
signals.append(
OperationalSignal(
signal_id=f"{scan.platform.platform_id}.repo.missing",
platform_id=scan.platform.platform_id,
kind=SignalKind.BLOCKER,
domain=GateDomain.REPOSITORY,
title="Repositorio local ausente",
summary="Nao existe espelho local para leitura ou validacao.",
severity=SignalSeverity.CRITICAL,
stage=HumanReadinessStage.NOT_FOUND,
sources=(SourceReference(path=scan.repo_path, summary="Caminho nao encontrado.", confidence=SourceConfidence.MISSING, role=EvidenceRole.ABSENT),),
tags=("repository", "missing"),
next_action="criar ou clonar o repositorio real",
)
)
if scan.git_present:
signals.append(
OperationalSignal(
signal_id=f"{scan.platform.platform_id}.git.present",
platform_id=scan.platform.platform_id,
kind=SignalKind.CAPABILITY,
domain=GateDomain.REPOSITORY,
title="Git local detectado",
summary="Branch, HEAD ou metadados Git foram detectados no repositorio.",
severity=SignalSeverity.INFO,
stage=HumanReadinessStage.TECHNICAL_READY,
sources=source_refs_from_strings((scan.branch or "branch desconhecida", scan.head or "head desconhecido", scan.remote_origin or "origin nao configurado"), "Metadado Git detectado."),
tags=("git",),
next_action="validar git status e sincronizacao no fechamento",
)
)
elif scan.exists:
signals.append(
OperationalSignal(
signal_id=f"{scan.platform.platform_id}.git.missing",
platform_id=scan.platform.platform_id,
kind=SignalKind.BLOCKER,
domain=GateDomain.REPOSITORY,
title="Git local nao detectado",
summary="Repositorio existe, mas .git nao foi detectado pela varredura.",
severity=SignalSeverity.HIGH,
stage=HumanReadinessStage.LOCAL_ONLY,
sources=(SourceReference(path=scan.repo_path, summary="Repositorio sem .git detectavel.", confidence=SourceConfidence.MISSING),),
tags=("git", "sync"),
next_action="inicializar Git ou corrigir permissao local de .git",
)
)
return tuple(signals)
def warning_signals(report: PlatformHumanReport) -> tuple[OperationalSignal, ...]:
signals: list[OperationalSignal] = []
for warning in report.scan.warnings:
matched = False
for rule_item in BLOCKER_RULES:
if rule_item.matches(warning):
refs = (SourceReference(path=report.scan.repo_path, summary=warning, confidence=SourceConfidence.DERIVED, role=EvidenceRole.PRIMARY),)
signals.append(signal_from_rule(report, rule_item, refs))
matched = True
if not matched:
signal_id = f"{report.platform.platform_id}.warning.{slugify(warning)}.{stable_digest(warning, 6)}"
signals.append(
OperationalSignal(
signal_id=signal_id,
platform_id=report.platform.platform_id,
kind=SignalKind.GAP,
domain=GateDomain.GOVERNANCE,
title="Warning de varredura",
summary=warning,
severity=SignalSeverity.MEDIUM,
stage=HumanReadinessStage.TECHNICAL_READY,
sources=(SourceReference(path=report.scan.repo_path, summary=warning, confidence=SourceConfidence.DERIVED),),
tags=("warning",),
next_action="classificar warning e registrar evidencia de resolucao ou excecao",
)
)
return tuple(signals)
def known_blocker_signals(report: PlatformHumanReport) -> tuple[OperationalSignal, ...]:
signals: list[OperationalSignal] = []
for blocker in report.platform.known_blockers:
matched_rules = [rule_item for rule_item in BLOCKER_RULES if rule_item.matches(blocker)]
if not matched_rules:
matched_rules = [
rule(
"known-blocker",
"Bloqueio conhecido catalogado",
SignalKind.BLOCKER,
GateDomain.GOVERNANCE,
SignalSeverity.HIGH,
HumanReadinessStage.BLOCKED,
report.platform.primary_categories,
(re.escape(blocker.lower()),),
"Bloqueio conhecido precisa ser resolvido, isolado ou formalizado.",
"resolver, isolar ou formalizar o bloqueio conhecido",
("known-blocker",),
)
]
for rule_item in matched_rules:
refs = (SourceReference(path=report.scan.repo_path, summary=blocker, confidence=SourceConfidence.DECLARED, role=EvidenceRole.PRIMARY),)
signals.append(signal_from_rule(report, rule_item, refs))
return tuple(signals)
def score_signals(report: PlatformHumanReport) -> tuple[OperationalSignal, ...]:
score = report.average_score
if score >= 90:
return (
OperationalSignal(
signal_id=f"{report.platform.platform_id}.score.high",
platform_id=report.platform.platform_id,
kind=SignalKind.CAPABILITY,
domain=GateDomain.GOVERNANCE,
title="Score humano alto",
summary=f"Score medio humano {score}; a plataforma tem boa cobertura por perfil.",
severity=SignalSeverity.INFO,
stage=HumanReadinessStage.HUMAN_EXPLAINABLE,
tags=("score",),
next_action="trocar score isolado por gates com blockers formais e evidencia viva",
),
)
if score >= 70:
severity = SignalSeverity.LOW
stage = HumanReadinessStage.HUMAN_EXPLAINABLE
elif score >= 50:
severity = SignalSeverity.MEDIUM
stage = HumanReadinessStage.TECHNICAL_READY
else:
severity = SignalSeverity.HIGH
stage = HumanReadinessStage.PLANNED
weakest = sorted(report.cells, key=lambda item: item.score)[:4]
evidence = tuple(
SourceReference(path=report.scan.repo_path, summary=f"{cell.profile_id}:{cell.score}", confidence=SourceConfidence.DERIVED)
for cell in weakest
)
return (
OperationalSignal(
signal_id=f"{report.platform.platform_id}.score.attention",
platform_id=report.platform.platform_id,
kind=SignalKind.GAP,
domain=GateDomain.GOVERNANCE,
title="Score humano exige melhoria",
summary=f"Score medio humano {score}; perfis mais frageis precisam de OS direcionada.",
severity=severity,
stage=stage,
sources=evidence,
tags=("score", "matrix"),
next_action="priorizar perfis de menor score em tela, relatorio ou comando humano",
),
)
def recommendation_signals(report: PlatformHumanReport, recommendations: Sequence[Recommendation]) -> tuple[OperationalSignal, ...]:
signals: list[OperationalSignal] = []
for rec in recommendations:
if rec.platform_id != report.platform.platform_id:
continue
severity = SignalSeverity.HIGH if rec.priority >= 85 else SignalSeverity.MEDIUM if rec.priority >= 65 else SignalSeverity.LOW
kind = SignalKind.BLOCKER if rec.priority >= 85 else SignalKind.DECISION
domain = domain_from_categories(rec.categories)
refs = source_refs_from_strings(rec.affected_paths or (report.scan.repo_path,), rec.reason, confidence=SourceConfidence.DECLARED)
signals.append(
OperationalSignal(
signal_id=f"{rec.recommendation_id}.{stable_digest(rec.reason, 6)}",
platform_id=report.platform.platform_id,
kind=kind,
domain=domain,
title=rec.title,
summary=rec.reason,
severity=severity,
stage=HumanReadinessStage.HUMAN_EXPLAINABLE,
categories=rec.categories,
sources=refs,
tags=("recommendation", rec.suggested_order_type.value),
next_action=rec.expected_impact,
)
)
return tuple(signals)
def domain_from_categories(categories: Sequence[NeedCategory]) -> GateDomain:
priority = {
NeedCategory.SECURITY: GateDomain.SECURITY,
NeedCategory.INTEGRATION: GateDomain.INTEGRATION,
NeedCategory.COMMERCIAL: GateDomain.BUSINESS,
NeedCategory.FINANCE: GateDomain.BUSINESS,
NeedCategory.LEGAL: GateDomain.COMPLIANCE,
NeedCategory.DOCUMENTATION: GateDomain.DOCS,
NeedCategory.EXPERIENCE: GateDomain.PANEL,
NeedCategory.OBSERVABILITY: GateDomain.OBSERVABILITY,
NeedCategory.GOVERNANCE: GateDomain.GOVERNANCE,
NeedCategory.OPERATIONS: GateDomain.RUNTIME,
}
for category in categories:
if category in priority:
return priority[category]
return GateDomain.GOVERNANCE
def dedupe_signals(signals: Iterable[OperationalSignal]) -> tuple[OperationalSignal, ...]:
seen: set[tuple[str, str, str]] = set()
output: list[OperationalSignal] = []
for signal in signals:
key = (signal.platform_id, signal.title.lower(), signal.domain.value)
if key in seen:
continue
seen.add(key)
output.append(signal)
output.sort(key=lambda item: (item.platform_id, -severity_to_sort(item.severity), item.domain.value, item.title))
return tuple(output)
def severity_to_sort(severity: SignalSeverity) -> int:
return {
SignalSeverity.CRITICAL: 5,
SignalSeverity.HIGH: 4,
SignalSeverity.MEDIUM: 3,
SignalSeverity.LOW: 2,
SignalSeverity.INFO: 1,
}.get(severity, 0)
def build_operational_signals(report: PlatformHumanReport, recommendations: Sequence[Recommendation] = ()) -> tuple[OperationalSignal, ...]:
signals: list[OperationalSignal] = []
signals.extend(repository_signals(report))
signals.extend(scan_rules(report, CAPABILITY_RULES))
signals.extend(scan_rules(report, BLOCKER_RULES))
signals.extend(scan_rules(report, PLATFORM_SPECIFIC_SIGNALS.get(report.platform.platform_id, ())))
signals.extend(warning_signals(report))
signals.extend(known_blocker_signals(report))
signals.extend(score_signals(report))
signals.extend(recommendation_signals(report, recommendations))
return dedupe_signals(signals)
def summarize_blockers(signals: Sequence[OperationalSignal], limit: int = 12) -> tuple[str, ...]:
blockers = [signal for signal in signals if signal.is_blocking]
blockers.sort(key=lambda item: (-severity_to_sort(item.severity), item.platform_id, item.title))
return merge_unique(f"{signal.platform_id}: {signal.title} - {signal.next_action}" for signal in blockers[:limit])
def summarize_capabilities(signals: Sequence[OperationalSignal], limit: int = 12) -> tuple[str, ...]:
caps = [signal for signal in signals if signal.kind == SignalKind.CAPABILITY]
caps.sort(key=lambda item: (item.platform_id, item.domain.value, item.title))
return merge_unique(f"{signal.platform_id}: {signal.title}" for signal in caps[:limit])
def scan_repository_for_order_text(repo_path: Path, patterns: Sequence[str], max_files: int = 80) -> tuple[SourceReference, ...]:
"""Search text files for specific order-related patterns.
The function is intentionally small and safe: it skips known build/vendor
directories, reads only bounded text files, and returns references rather
than raw content.
"""
skip = {".git", "node_modules", "dist", "build", "coverage", "__pycache__", ".wrangler", "vendor"}
extensions = {".md", ".ts", ".tsx", ".js", ".mjs", ".cjs", ".py", ".json", ".toml", ".yml", ".yaml"}
refs: list[SourceReference] = []
if not repo_path.exists():
return (
SourceReference(path=str(repo_path), summary="Repositorio nao encontrado para busca de texto.", confidence=SourceConfidence.MISSING, role=EvidenceRole.ABSENT),
)
stack = [repo_path]
while stack and len(refs) < max_files:
current = stack.pop()
try:
entries = sorted(current.iterdir(), key=lambda item: item.name.lower())
except OSError:
continue
for entry in entries:
if entry.is_dir():
if entry.name not in skip:
stack.append(entry)
continue
if entry.suffix.lower() not in extensions:
continue
try:
if entry.stat().st_size > 240_000:
continue
lines = entry.read_text(encoding="utf-8", errors="ignore").splitlines()
except OSError:
continue
rel = str(entry.relative_to(repo_path)).replace("\\", "/")
for index, line in enumerate(lines, start=1):
lowered = line.lower()
if any(re.search(pattern, lowered, re.I) for pattern in patterns):
refs.append(
SourceReference(
path=rel,
line=index,
summary="Trecho local referencia tema da ordem sem expor conteudo bruto.",
confidence=SourceConfidence.DIRECT,
role=EvidenceRole.SUPPORTING,
)
)
break
if len(refs) >= max_files:
break
return tuple(refs)