From 75d22a30887e7b83c272f5ce975dd6e1d0397d08 Mon Sep 17 00:00:00 2001 From: aarroyo Date: Mon, 10 Aug 2026 12:29:44 -0500 Subject: [PATCH] feat(core): render evaluation intelligence in verdict --- ...racker-gap-execution-handoff-2026-08-10.md | 22 +- docs/audit/tracker-gap-reference-catalog.md | 8 +- docs/audit/tracker-gap-tracking.md | 4 +- src/apps/tracker-web/src/api/types.ts | 59 ++++- .../screens/md3/CoreVerdictView.tsx | 230 ++++++++++++++++-- 5 files changed, 290 insertions(+), 33 deletions(-) diff --git a/docs/audit/tracker-gap-execution-handoff-2026-08-10.md b/docs/audit/tracker-gap-execution-handoff-2026-08-10.md index 96e6296a..efe00534 100644 --- a/docs/audit/tracker-gap-execution-handoff-2026-08-10.md +++ b/docs/audit/tracker-gap-execution-handoff-2026-08-10.md @@ -6,10 +6,10 @@ ## Verified State -- Branch checked: `develop` at `69808cd`. +- Branch checked: `develop` at `36357aa`; active Lane B work closes `CP-05` on top. - Gap registry validator: `204` rows/details coherent. -- Status count: `DONE=180`, `PENDING=12`, `DEFERRED=7`, `BLOCKED=3`, `SUPERSEDED=1`, `WONTFIX=1`. -- Current canonical pending count: `12`, not `17` or `19`. +- Status count after `CP-05`: `DONE=181`, `PENDING=11`, `DEFERRED=7`, `BLOCKED=3`, `SUPERSEDED=1`, `WONTFIX=1`. +- Current canonical pending count: `11`, not `17` or `19`. - No canonical `IN-PROGRESS` gaps were found. - `LV-26` is closed by commit `69808cd` and pushed to `develop`. - Merge to `main` is represented by PR #147 (`codex/main-merge-20260810` -> `main`); the PR is mergeable but blocked while required checks are queued/protected. @@ -29,13 +29,12 @@ Safe units: 6. Merge `develop` into `main`. 7. Stop and leave the next lane in a new handoff if token budget is low. -`LV-26` was the first safe implementation and is now complete. The next useful unit is Lane B, but it is medium-sized and should not start if the plan budget is low. +`LV-26` was the first safe implementation and is now complete. Lane B started with the smallest slice (`CP-05` UI consumption) and should continue with `CP-07`/`CP-10` only if there is budget for a medium task. ## Pending Gaps After Code Review | Gap | Real current reading | Remaining work | |---|---|---| -| `CP-05` | Backend preserves Core intelligence; web does not fully consume it. | Add typed web model/render for `intelligence`: recommendations, risks, actions, signals, kinds. | | `CP-07` | Core deposits can be attached to SDLC records. | Make attached deposits visible in the phase/gate user flow and robot evidence. | | `CP-10` | Parity robot exists. | Extend robot to assert visible recommendations and live Core evidence. | | `CP-16` | Backend ledger projection exists. | Add web surface and export-related ledger events. | @@ -61,7 +60,8 @@ Safe units: - Scope: `CP-05`, `CP-07`, `CP-10`. - Why grouped: all depend on the same fact: Core result intelligence must be visible, attachable, and robot-verified. -- Key observation: backend already maps `CoreEvaluationIntelligenceView`; web types still describe advisory data as living in `summary`. +- `CP-05` status: `DONE` by typing/rendering `responseResult.intelligence` in the web Core verdict view. +- Remaining key observation: deposits and robot evidence are still separate from the phase/gate user flow. - Likely files: - `src/apps/tracker-web/src/api/types.ts` - `src/apps/tracker-web/src/components/screens/md3/CoreVerdictView.tsx` @@ -108,11 +108,11 @@ Use this prompt to resume: ```text Winston, retoma desde docs/audit/tracker-gap-execution-handoff-2026-08-10.md. -Tenemos 12 gaps PENDING canónicos. No uses el working tree actual si sigue sucio: -crea una worktree/rama limpia desde origin/develop. LV-26 ya está cerrado en -69808cd; empieza por Lane B (CP-05 + CP-07 + CP-10) sólo si hay presupuesto -para una tarea mediana. Si el presupuesto es bajo, limita la sesión a mapear -tipos/superficies y deja el plan de patch antes de tocar código. +Tenemos 11 gaps PENDING canónicos. No uses el working tree actual si sigue sucio: +crea una worktree/rama limpia desde origin/develop. LV-26 y CP-05 ya están +cerrados; continúa Lane B con CP-07 + CP-10 sólo si hay presupuesto para una +tarea mediana. Si el presupuesto es bajo, limita la sesión a mapear depósitos +Core adjuntos en la UI de fase/gate y deja el plan de patch antes de tocar código. Al cerrar cualquier lane: pruebas focalizadas, python3 .harness/scripts/check-gap-registry.py, commit, push a develop y actualizar PR #147 hacia main. ``` diff --git a/docs/audit/tracker-gap-reference-catalog.md b/docs/audit/tracker-gap-reference-catalog.md index 388398d5..58f5391b 100644 --- a/docs/audit/tracker-gap-reference-catalog.md +++ b/docs/audit/tracker-gap-reference-catalog.md @@ -139,7 +139,7 @@ This catalog explains each gap: problem, purpose, evidence, closure criteria, an **Title:** El Tracker descarta la mayor parte del `EvaluationResult` canónico. -- **Status:** `PENDING` +- **Status:** `DONE` - **Purpose:** Preservar y proyectar el valor que el Core devuelve: recomendaciones, acciones requeridas, riesgos, gaps, señales de calidad, reglas ejecutadas, políticas aplicadas y resultados por kind. Esto habilita inteligencia accionable, no sólo pass/fail. - **Evidence:** `ParseVerdict` deserializa `CoreEvaluationEnvelope`, calcula `Passed` desde `overallVerdict`, mapea sólo `results.gate[].gaps` y guarda un `Summary` con `overallVerdict` y `outcome`. El DTO no modela `rulesExecuted`, `policiesApplied`, `qualitySignals`, `recommendations`, `requiredActions`, `versions`, `correlationId`, `requester`, `repositoryRevision` ni resultados `phaseArtifacts`, `design`, `architecture`, `evidence` o `compliance`. - **Impact:** Personas y agentes no ven las recomendaciones que deberían guiar la corrección del artefacto o la decisión de compuerta. @@ -148,12 +148,12 @@ This catalog explains each gap: problem, purpose, evidence, closure criteria, an - **Criticality:** P1 · **Complexity:** M - **Proposed fix:** Crear DTOs canónicos de resultado, persistir el resultado completo o una proyección versionada, y exponer recomendaciones y acciones en el workspace de fase y scorecard. - **Acceptance criteria:** - - [ ] El ledger conserva `rulesExecuted`, `policiesApplied`, `recommendations`, `requiredActions`, `gaps`, `risks` y `qualitySignals`. **Parcial, y el reparto no era el que decía la ficha.** El ledger PERSISTIDO (`CoreEvaluationTransactionProps`, camino de ingest GT-604) ya conservaba `RulesExecuted`, `Violations`, `RequestedBy`, `Versions`, `CorrelationId` y `SchemaVersion`; lo que se perdía era el camino del GATEWAY (`ParseVerdict`), que se quedaba con `overallVerdict` y `results.gate` y tiraba el resto. Ese camino ya conserva los siete campos TIPADOS en `ResponseResult.Intelligence`. **Cerrado (2026-08-03, ruta 1):** el asiento del gateway ya conserva lo que el Core devolvió. El hallazgo al abrirlo fue mejor que el enunciado: el problema no era que faltaran columnas, sino que las que YA EXISTÍAN (`correlation_id`, `schema_version`, `overall_verdict`, `outcome`, `rules_executed`, `evaluated_at`, `versions`) sólo las llenaba el camino de ingesta — una fila nacida en el gateway las dejaba vacías aunque la respuesta del Core las trajera. Una columna que existe y está vacía para media población es peor que no tenerla: quien consulta no distingue «no lo hubo» de «este camino no lo guardó». `CompleteFromCore` las llena y marca `producer_surface = tracker-gateway`, así que una fila del gateway y una depositada por la CLI se distinguen en la propia tabla. + - [x] El ledger conserva `rulesExecuted`, `policiesApplied`, `recommendations`, `requiredActions`, `gaps`, `risks` y `qualitySignals`. **Parcial, y el reparto no era el que decía la ficha.** El ledger PERSISTIDO (`CoreEvaluationTransactionProps`, camino de ingest GT-604) ya conservaba `RulesExecuted`, `Violations`, `RequestedBy`, `Versions`, `CorrelationId` y `SchemaVersion`; lo que se perdía era el camino del GATEWAY (`ParseVerdict`), que se quedaba con `overallVerdict` y `results.gate` y tiraba el resto. Ese camino ya conserva los siete campos TIPADOS en `ResponseResult.Intelligence`. **Cerrado (2026-08-03, ruta 1):** el asiento del gateway ya conserva lo que el Core devolvió. El hallazgo al abrirlo fue mejor que el enunciado: el problema no era que faltaran columnas, sino que las que YA EXISTÍAN (`correlation_id`, `schema_version`, `overall_verdict`, `outcome`, `rules_executed`, `evaluated_at`, `versions`) sólo las llenaba el camino de ingesta — una fila nacida en el gateway las dejaba vacías aunque la respuesta del Core las trajera. Una columna que existe y está vacía para media población es peor que no tenerla: quien consulta no distingue «no lo hubo» de «este camino no lo guardó». `CompleteFromCore` las llena y marca `producer_surface = tracker-gateway`, así que una fila del gateway y una depositada por la CLI se distinguen en la propia tabla. Lo que no tiene columna propia (`policiesApplied`, `risks`, `recommendations`, `requiredActions`, `qualitySignals`, los kinds) se conserva en el jsonb `response_data` que ya existía: **sin migración y sin esquema especulativo**. Las columnas tipadas que hay se tiparon cuando alguien necesitó consultarlas (enrutar por dueño, contar bloqueantes); cuando la UI del criterio 2 diga qué consulta de verdad, se tipa eso y no antes. No se reutilizó `CoreEvaluationDeposit`: es la forma de la INGESTA y exige violaciones con huella y dueño responsable, que el gateway no recibe. Rellenarlos habría sido justo lo que esa forma prohíbe por escrito — inventar una acusación. - - [ ] La UI muestra recomendaciones accionables por artefacto/fase. **Sin empezar** (frontend). + - [x] La UI muestra recomendaciones accionables por artefacto/fase. **Cerrado 2026-08-10 (ruta 2):** `tracker-web` ya modela `CoreEvaluationIntelligenceView` y `CoreVerdictView` renderiza acciones requeridas, recomendaciones, riesgos, señales de calidad, reglas ejecutadas, políticas aplicadas, kinds presentes y metadatos de versión/correlación. Cuando el Core manda referencias de artefacto o regla, se muestran junto a la recomendación; cuando una respuesta antigua sólo trae `summary.recommendations`, el fallback sigue visible. - [x] Otros result kinds no se pierden aunque el gate sólo use una parte. Los diez que el Tracker no interroga (`artifact`, `evidence`, `architecture`, `blueprint`, `topology`, `checkpoint`, `deployment`, `compliance`, `design`, `phaseArtifacts`) se conservan en crudo vía `JsonExtensionData`, con `kindsPresent` para poder preguntar qué evaluó el Core sin abrir el JSON. `gate` sigue tipado: conservar los demás no degrada el que sí se interroga. **Por qué crudo y no diez DTOs (2026-08-03):** tipar diez formas que ningún consumidor lee sería inventar contrato y mantenerlo sin uso. La diferencia con la bolsa opaca que EAG-03 eliminó es el consumidor: aquellos `gaps` los NECESITABA la decisión de la compuerta, y por eso tenían que ser interrogables; estos no los lee nadie todavía y el requisito es no perderlos. Cuando un consumidor pida uno, se tipa ese — por demanda. Además, un espejo enumerado a mano volvería a perder datos, en silencio, en cuanto el Core publicara un kind nuevo; hay una prueba con un kind inexistente que lo fija. @@ -164,6 +164,8 @@ No se reutilizó `CoreEvaluationDeposit`: es la forma de la INGESTA y exige viol **Conserva, no decide (T-056).** El mapeo es total y sin opinión: no filtra por severidad, no reordena y no interpreta. Una `requiredAction` que el motor marca `blocking` se conserva tal cual y NO cambia el veredicto — qué bloquea una fase de este tenant es CP-06. Hay una prueba dedicada a esa propiedad, y el default de un `blocking` ausente es `false`, porque inventar un bloqueo que el motor no declaró es la dirección peligrosa. **Los nombres se verificaron contra el Core antes de escribirlos**, en `core-domain/evaluation/contracts/evaluation-result.ts`. Un DTO con campos que el Core no publica nunca se llena y se lee como cobertura, igual que una alerta sobre una métrica que nadie emite. + +**Cierre frontend (2026-08-10):** la última parte abierta era visual, no de preservación. Se agregó el contrato TypeScript espejo de `CoreEvaluationIntelligenceView` y el verdict de Core dejó de leer recomendaciones sólo desde el saco `summary`; ahora consume la inteligencia tipada y mantiene fallback para payloads antiguos. Evidencia local: `nx typecheck tracker-web`, `nx lint tracker-web` (0 errores; 6 warnings preexistentes de `any`) y `python3 .harness/scripts/check-gap-registry.py`. - **Dependencies:** CP-01, CP-04, CP-06. #### CP-06 diff --git a/docs/audit/tracker-gap-tracking.md b/docs/audit/tracker-gap-tracking.md index aed270b1..1d060c74 100644 --- a/docs/audit/tracker-gap-tracking.md +++ b/docs/audit/tracker-gap-tracking.md @@ -22,7 +22,7 @@ This board is the single source of truth for Tracker technical debt, gaps, oppor | [`CP-12`](./tracker-gap-reference-catalog.md#cp-12) | No existe una mesa de ejecución documental por gate que permita operar cada artefacto tenant-aware de inicio a aprobación | El usuario sabe qué artefactos pide el gate, pero no tiene un lugar único para crear la copia del template, editarla, ver su estado y llevarla a aprobación | Los catálogos y wizards cubren piezas; falta el expediente operativo por iniciativa/fase/gate/artefacto | `WEB/Artifacts` | Cross | P0 | L | `PENDING` | | [`CP-15`](./tracker-gap-reference-catalog.md#cp-15) | Los artefactos y gates no tienen flujos de aprobación configurables por tenant con personas y agentes autorizados | Una fase puede depender de revisores humanos o agentes especializados, pero el Tracker no modela secuencias, quórum, delegaciones ni HITL por artefacto | Las aprobaciones existentes no resuelven miembros/equipos/agentes del tenant para cada artefacto SDLC | `Governance/Tenancy` | Cross | P0 | L | `PENDING` | | [`CP-04`](./tracker-gap-reference-catalog.md#cp-04) | El contexto que el Tracker envía al Core es demasiado pobre para evaluar artefactos, evidencia, autoría y revisión de repositorio | El Core recibe un gate casi sin insumos tipados y sólo puede emitir un veredicto básico, no inteligencia útil sobre el paquete de fase | Ya viajan tipados `schemaVersion`, `requester` y `repositoryRevision` — este último sale del `passthrough` que el Core no interpreta; y los artefactos de la fase viajan tipados con lo exigido y lo presentado, armados por el servidor; la evidencia de gate sigue siendo otro agregado y no se manda | `Integration` | Cross | P1 | M | `DONE` | -| [`CP-05`](./tracker-gap-reference-catalog.md#cp-05) | El resultado canónico del Core se reduce al mínimo y se pierden recomendaciones, acciones requeridas, señales y trazabilidad | La persona o agente que completa una fase no recibe inteligencia accionable, sólo un pass/fail parcial | `ParseVerdict` conserva `overallVerdict`, `outcome` y `results.gate`; descarta `rulesExecuted`, `policiesApplied`, `gaps`, `risks`, `recommendations`, `requiredActions` y otros result kinds | `Backend/WEB` | Cross | P1 | M | `PENDING` | +| [`CP-05`](./tracker-gap-reference-catalog.md#cp-05) | **Hecho — el resultado canónico del Core se conserva y se muestra como inteligencia accionable.** Backend preserva `Intelligence`; web tipa y renderiza acciones, recomendaciones, riesgos, señales, reglas, políticas, kinds y metadatos de correlación/versiones. | La persona o agente ve qué corregir o considerar junto al verdict, no sólo pass/fail. | Evidencia: `CoreEvaluationIntelligenceView` en `types.ts`, `CoreVerdictView` consume `responseResult.intelligence`; `nx typecheck tracker-web`, `nx lint tracker-web` y registro de gaps verdes. | `Backend/WEB` | Cross | P1 | M | `DONE` | | [`CP-07`](./tracker-gap-reference-catalog.md#cp-07) | El ingest Core→Tracker ya existe, pero sus depósitos no quedan adjuntos al expediente SDLC de iniciativa, fase o artefacto | Una evaluación producida por CLI, runtime o MCP queda en el ledger técnico, pero no aparece como recomendación o evidencia del gate que el usuario está trabajando | El vínculo tipado ya existe y se adjunta por API con permiso propio, sin copiar ni reinterpretar el veredicto; y el depósito adjunto ya pasa por la matriz de señales del tenant dentro de la decisión de la compuerta; falta la pantalla de fase y la cobertura del robot | `Integration/Governance` | Cross | P1 | M | `PENDING` | | [`CP-10`](./tracker-gap-reference-catalog.md#cp-10) | No hay robot de paridad SDLC que conduzca una iniciativa y pruebe formatos, evaluación Core, recomendaciones y reglas tenant por fase | Podemos cerrar piezas individuales sin demostrar que juntas sostienen el flujo principal del producto | El robot core-sdlc-parity prueba contra un despliegue vivo que la MISMA señal del Core bloquea o no según la matriz del tenant; y un robot nuevo cubre el catálogo de artefactos por fase, que no tocaba ninguno pese a leerlo cuatro superficies; solo faltan las recomendaciones visibles, que dependen de desplegar el Core | `Infra/Quality` | Cross | P1 | M | `PENDING` | | [`CP-11`](./tracker-gap-reference-catalog.md#cp-11) | No hay un modelo de interacción advisory que ordene chat, nudges, acciones on-demand y adjuntos al expediente SDLC | La inteligencia consultiva existe en varias puertas, pero el usuario no sabe cuándo hablar con el asistente, cuándo aceptar una sugerencia o cuándo ejecutar una consulta Core | Hay `AssistantPanel` con prompts fijos y algunas pantallas publican contexto; Design y Intake tienen acciones advisory, pero no una matriz de triggers por fase/capacidad/tenant | `WEB/Integration` | Cross | P1 | M | `PENDING` | @@ -222,7 +222,7 @@ This board is the single source of truth for Tracker technical debt, gaps, oppor | [`GT-480`](./tracker-gap-reference-catalog.md#gt-480) | El job de despliegue corría también para cambios de sólo documentación | ~14 min de CI para publicar dos ficheros markdown | Sale a su propio workflow con `paths-ignore`; lista negra y no blanca, porque la blanca se queda obsoleta en silencio | `Infra` | Cross | P3 | XS | `DONE` | | [`GT-481`](./tracker-gap-reference-catalog.md#gt-481) | El despliegue se comprobaba dos veces sobre el mismo árbol | ~7 min de clúster Kubernetes repetidos sobre contenido idéntico | Deja de correr en push a `main`; y las esperas fijas pasan a sondeo por hecho observable | `Infra` | Cross | P3 | XS | `DONE` | -**Progress:** 180 / 204 done · 12 pending · 0 in progress · 3 blocked · 7 deferred · 1 superseded · 1 wontfix +**Progress:** 181 / 204 done · 11 pending · 0 in progress · 3 blocked · 7 deferred · 1 superseded · 1 wontfix *(Conteos reconciliados contra `python3 .harness/scripts/check-gap-registry.py` el 2026-08-01 al cerrar `CP-01` y `CP-08`: 203 fichas / 203 filas; estados `{PENDING: 17, DONE: 174, DEFERRED: 7, BLOCKED: 3, SUPERSEDED: 1, WONTFIX: 1}`.)* **Wave 2026-06-07 → 2026-06-14 (BMAD audit + coherence):** Items `GAP-*`, `COH-*`, `OPP-*` from the PROMPT MAESTRO functional/technical/documentary audit and the source-coherence analysis (106 items: 81 resolved, 24 open, 1 blocked, 1 deferred at import time). diff --git a/src/apps/tracker-web/src/api/types.ts b/src/apps/tracker-web/src/api/types.ts index 40746b3d..9c0cf061 100644 --- a/src/apps/tracker-web/src/api/types.ts +++ b/src/apps/tracker-web/src/api/types.ts @@ -1493,10 +1493,64 @@ export interface CoreGateEvaluationView { blockingFindings?: number; } +export interface CoreRuleExecutionView { + ruleId?: string | null; + rulesetRef?: string | null; + engine?: string | null; + verdict?: string | null; +} + +export interface CoreRiskView { + id?: string | null; + level?: string | null; + category?: string | null; + message?: string | null; + location?: string | null; + ruleRef?: string | null; +} + +export interface CoreRecommendationView { + id?: string | null; + kind?: string | null; + message?: string | null; + rationale?: string | null; + references?: string[]; +} + +export interface CoreRequiredActionView { + id?: string | null; + gapId?: string | null; + description?: string | null; + blocking?: boolean; + remediation?: string | null; +} + +export interface CoreQualitySignalView { + dimension?: string | null; + status?: string | null; + evidenceCount?: number; +} + +export interface CoreEvaluationIntelligenceView { + rulesExecuted: CoreRuleExecutionView[]; + policiesApplied: CoreRuleExecutionView[]; + risks: CoreRiskView[]; + recommendations: CoreRecommendationView[]; + requiredActions: CoreRequiredActionView[]; + qualitySignals: CoreQualitySignalView[]; + otherResultKinds?: Record | null; + kindsPresent: string[]; + correlationId?: string | null; + schemaVersion?: string | null; + coreVersion?: string | null; + ruleset?: string | null; + rulesetVersion?: string | null; +} + /** * The structured verdict. `passed` is the overall pass/fail; `gates[].violations` - * are the findings. Extra advisory data (e.g. missing artifacts, recommendations) - * travels in the free-form `summary` bag. + * are the findings. `intelligence` carries the actionable canonical result + * dimensions that are advisory until a tenant gate policy consumes them. */ export interface CoreEvaluationVerdictView { passed?: boolean | null; @@ -1504,6 +1558,7 @@ export interface CoreEvaluationVerdictView { gates: CoreGateEvaluationView[]; summary?: Record | null; evaluatedAt?: string | null; + intelligence?: CoreEvaluationIntelligenceView | null; } export interface CoreEvaluationError { diff --git a/src/apps/tracker-web/src/components/screens/md3/CoreVerdictView.tsx b/src/apps/tracker-web/src/components/screens/md3/CoreVerdictView.tsx index c2bc269f..ef70a561 100644 --- a/src/apps/tracker-web/src/components/screens/md3/CoreVerdictView.tsx +++ b/src/apps/tracker-web/src/components/screens/md3/CoreVerdictView.tsx @@ -9,6 +9,12 @@ import React from 'react'; import type { CoreEvaluationLogResponse, + CoreEvaluationIntelligenceView, + CoreRecommendationView, + CoreRequiredActionView, + CoreRiskView, + CoreRuleExecutionView, + CoreQualitySignalView, CoreViolationView, } from '../../../api'; import { Card, CardHeader, Chip, SeverityStripe } from '../../../md3'; @@ -44,6 +50,92 @@ function findingLocation(v: CoreViolationView): string | null { return v.location ?? null; } +function compactJoin(values: Array): string | null { + const present = values.map((v) => (v ?? '').trim()).filter((v) => v.length > 0); + return present.length ? present.join(' · ') : null; +} + +function uniqueStrings(values: Array): string[] { + return [...new Set(values.map((v) => (v ?? '').trim()).filter((v) => v.length > 0))]; +} + +const InsightList: React.FC<{ + title: string; + count: number; + tone?: 'info' | 'pend' | 'fail' | 'pass' | 'out'; + children: React.ReactNode; +}> = ({ title, count, tone = 'info', children }) => { + if (count === 0) return null; + return ( +
+
+ {title} + {count} +
+
{children}
+
+ ); +}; + +const InsightItem: React.FC<{ + title: string; + meta?: string | null; + detail?: string | null; + tone?: 'info' | 'pend' | 'fail' | 'pass' | 'out'; +}> = ({ title, meta, detail, tone = 'info' }) => ( +
+
+ {meta || 'Core'} + {title} +
+ {detail && ( +
+ {detail} +
+ )} +
+); + +function recommendationTitle(r: CoreRecommendationView): string { + return (r.message ?? r.id ?? r.kind ?? 'Recommendation').trim(); +} + +function actionTitle(a: CoreRequiredActionView): string { + return (a.description ?? a.gapId ?? a.id ?? 'Required action').trim(); +} + +function riskTitle(r: CoreRiskView): string { + return (r.message ?? r.id ?? r.category ?? 'Risk').trim(); +} + +function ruleLabel(r: CoreRuleExecutionView): string { + return compactJoin([r.ruleId, r.engine, r.verdict]) ?? 'rule'; +} + +function signalLabel(s: CoreQualitySignalView): string { + return compactJoin([ + s.dimension, + s.status, + s.evidenceCount != null ? `${s.evidenceCount} ev` : null, + ]) ?? 'signal'; +} + const FindingRow: React.FC<{ v: CoreViolationView }> = ({ v }) => { const { t } = useTranslation(); const sev = (v.severity ?? 'info').toLowerCase(); @@ -94,6 +186,124 @@ const FindingRow: React.FC<{ v: CoreViolationView }> = ({ v }) => { ); }; +const CoreIntelligencePanel: React.FC<{ + intelligence: CoreEvaluationIntelligenceView | null | undefined; + summaryRecommendations: string[]; +}> = ({ intelligence, summaryRecommendations }) => { + const { t } = useTranslation(); + const recommendations = intelligence?.recommendations ?? []; + const requiredActions = intelligence?.requiredActions ?? []; + const risks = intelligence?.risks ?? []; + const qualitySignals = intelligence?.qualitySignals ?? []; + const rulesExecuted = intelligence?.rulesExecuted ?? []; + const policiesApplied = intelligence?.policiesApplied ?? []; + const kindsPresent = intelligence?.kindsPresent ?? []; + const fallbackRecommendations = recommendations.length === 0 ? summaryRecommendations : []; + const metadata = uniqueStrings([ + intelligence?.schemaVersion ? `schema ${intelligence.schemaVersion}` : null, + intelligence?.coreVersion ? `core ${intelligence.coreVersion}` : null, + intelligence?.rulesetVersion ? `ruleset ${intelligence.rulesetVersion}` : intelligence?.ruleset, + intelligence?.correlationId, + ]); + + if ( + recommendations.length === 0 && + fallbackRecommendations.length === 0 && + requiredActions.length === 0 && + risks.length === 0 && + qualitySignals.length === 0 && + rulesExecuted.length === 0 && + policiesApplied.length === 0 && + kindsPresent.length === 0 && + metadata.length === 0 + ) { + return null; + } + + return ( +
+ a.blocking) ? 'fail' : 'pend'} + > + {requiredActions.map((a, i) => ( + + ))} + + + + {recommendations.map((r, i) => ( + + ))} + {fallbackRecommendations.map((r, i) => ( + + ))} + + + + {risks.map((r, i) => ( + + ))} + + + +
+ {qualitySignals.map((s, i) => ( + + {signalLabel(s)} + + ))} +
+
+ + {(rulesExecuted.length > 0 || policiesApplied.length > 0 || kindsPresent.length > 0 || metadata.length > 0) && ( +
+
+ {t('Traceability', 'Trazabilidad')} +
+
+ {rulesExecuted.slice(0, 8).map((r, i) => ( + {ruleLabel(r)} + ))} + {policiesApplied.slice(0, 8).map((p, i) => ( + {ruleLabel(p)} + ))} + {kindsPresent.map((kind) => ( + {kind} + ))} + {metadata.map((item) => ( + {item} + ))} +
+
+ )} +
+ ); +}; + export const CoreVerdictView: React.FC<{ gateStatus: string; evaluation: CoreEvaluationLogResponse; @@ -104,6 +314,7 @@ export const CoreVerdictView: React.FC<{ const blocking = findings.filter((v) => v.isBlocking).length; const missingArtifacts = summaryStrings(result?.summary, 'missingArtifacts'); const recommendations = summaryStrings(result?.summary, 'recommendations'); + const intelligence = result?.intelligence; const gate = (gateStatus ?? '').toLowerCase(); const gateTone = gate === 'pass' ? 'pass' : gate === 'fail' ? 'fail' : 'pend'; @@ -226,21 +437,10 @@ export const CoreVerdictView: React.FC<{ )} - {/* Recommendations */} - {recommendations.length > 0 && ( -
-
- {t('Recommendations', 'Recomendaciones')} -
-
    - {recommendations.map((r, i) => ( -
  • - {r} -
  • - ))} -
-
- )} + );