test(ucepsa): add context regression and hardening suite

This commit is contained in:
Victor Fraile Garcia 2026-07-20 18:25:39 +02:00
parent b06f15534f
commit 3d0aef0410
7 changed files with 3385 additions and 2 deletions

View File

@ -0,0 +1,174 @@
# UCEPSA — Regresión y hardening SHADOW v0.3.13
## Objetivo
Proteger automáticamente los comportamientos ya validados:
```text
cierre Shop Floor
arbitraje canónico
contexto legacy
conflictos sin prioridad silenciosa
casos raíz
decisiones consolidadas
agenda diaria
snapshots
readiness
guardas oficiales
```
La suite no modifica Odoo ni datos de producción.
## Contratos principales
```text
WO 340:
sesión cerrada
100 kg finales
Esteban confirmado
sin extensión posterior al cierre
Episodio 1:
LEGACY_ERP autorizado
intervalo exacto
cobertura canónica completa
WO 649/698:
conflicto histórico conservado
sin clave canónica
Gobernanza:
7/8/9 → un caso raíz
1-4, 5-6 y 7-8 → tres decisiones
agenda = backlog
snapshot 1 coherente
SHADOW:
cero filas oficiales
```
## Objetos
```text
context_regression_policies
context_regression_runs
context_regression_results
li_context_regression_latest_run_v1
li_context_regression_latest_results_v1
li_context_regression_failures_v1
li_context_regression_run_history_v1
li_context_regression_health_v1
```
## Despliegue
```bash
FILE=/srv/mesavault/40-clients/ucepsa/edge-oee-demo/sql/versions/115_ucepsa_context_regression_hardening_shadow_v0313.sql
docker exec -i mv_ucepsa_postgres_hot sh -lc \
'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"' \
< "$FILE"
```
## Runtime
```bash
RUNTIME=/srv/mesavault/edge-oee-ucepsa
TARGET=/srv/mesavault/40-clients/ucepsa/edge-oee-demo
install -m 0644 \
"$TARGET/tools/context_regression_suite.py" \
"$RUNTIME/tools/"
```
## Ejecución previa sin persistencia
```bash
MANAGER=/srv/mesavault/40-clients/ucepsa/edge-oee-demo/ops/manage_context_regression_v0313.sh
"$MANAGER" dry-run \
--triggered-by "Víctor Fraile"
```
## Primera ejecución persistente
```bash
"$MANAGER" run \
--triggered-by "Víctor Fraile" \
--trigger-kind DEPLOYMENT \
--notes "Validación posterior al despliegue v0.3.13."
```
`PASS` es el resultado ideal. `WARN` es admisible si se limita a salud o
rendimiento. `FAIL` o `ERROR` requiere investigación antes de continuar.
## Consultas
```bash
"$MANAGER" show
"$MANAGER" list --limit 20
"$MANAGER" show --run-id NUMERO
```
## Exportar Markdown
```bash
OUT=/srv/mesavault/exports/context-regression-$(date +%F).md
"$MANAGER" export-markdown \
> "$OUT"
sed -n '1,260p' "$OUT"
```
## Validación
```bash
VALIDATION=/srv/mesavault/40-clients/ucepsa/edge-oee-demo/ops/validate_context_regression_v0313.sql
docker exec -i mv_ucepsa_postgres_hot sh -lc \
'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"' \
< "$VALIDATION"
```
Valores obligatorios:
```text
run_count_mismatches = 0
duplicate_test_codes = 0
suite_complete = true
latest_fail_or_error_count = 0
critical_contract_not_passed = 0
official_run_rows = 0
official_result_rows = 0
official_health_rows = 0
official_operator_queue_rows = 0
```
## Uso operativo recomendado
Ejecutar la suite:
```text
después de cada migración
después de modificar sincronizadores o arbitraje
antes de desplegar el dashboard
antes de activar cualquier nueva fase de readiness
```
La ejecución periódica puede programarse después de acordar la hora adecuada.
No se instala ningún cron automáticamente en esta versión.
## Dashboard
Se mantiene el mismo UID y se añaden:
```text
Última ejecución de regresión
Warnings y fallos
Histórico de regresión
```
Paneles totales esperados: 31.

View File

@ -15,7 +15,7 @@
} }
] ]
}, },
"description": "Supervisión SHADOW de sesiones, arbitraje, casos raíz, decisiones humanas y agenda diaria de gobernanza. La revisión se organiza por preguntas operativas y puede guardarse como snapshot auditable.", "description": "Supervisión SHADOW de sesiones, gobernanza diaria y regresión funcional. La suite protege contratos históricos, arbitraje, agrupación, agenda y guardas oficiales sin modificar datos productivos.",
"editable": true, "editable": true,
"fiscalYearStartMonth": 0, "fiscalYearStartMonth": 0,
"graphTooltip": 1, "graphTooltip": 1,
@ -3286,6 +3286,360 @@
], ],
"title": "Indicadores de gobernanza diaria", "title": "Indicadores de gobernanza diaria",
"type": "table" "type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "bfnbcasbm6hhca"
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"cellOptions": {
"type": "auto"
},
"filterable": true,
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Estado"
},
"properties": [
{
"id": "mappings",
"value": [
{
"type": "value",
"options": {
"PASS": {
"text": "PASS",
"color": "green",
"index": 0
},
"WARN": {
"text": "WARN",
"color": "orange",
"index": 1
},
"FAIL": {
"text": "FAIL",
"color": "red",
"index": 2
},
"ERROR": {
"text": "ERROR",
"color": "red",
"index": 3
}
}
}
]
},
{
"id": "custom.cellOptions",
"value": {
"type": "color-background"
}
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 218
},
"id": 29,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": []
},
"pluginVersion": "11.0.0",
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "bfnbcasbm6hhca"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "SELECT\n run_id AS \"Ejecución\",\n suite_version AS \"Suite\",\n overall_status AS \"Estado\",\n triggered_by AS \"Disparada por\",\n trigger_kind AS \"Tipo\",\n started_at AS \"Inicio\",\n finished_at AS \"Fin\",\n duration_ms AS \"Duración ms\",\n result_count AS \"Tests\",\n pass_count AS \"PASS\",\n warn_count AS \"WARN\",\n fail_count AS \"FAIL\",\n error_count AS \"ERROR\",\n source_git_commit AS \"Commit\"\nFROM\n mv_reports_ucepsa_prod\n .li_context_regression_latest_run_v1",
"refId": "A"
}
],
"title": "Última ejecución de regresión",
"type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "bfnbcasbm6hhca"
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"cellOptions": {
"type": "auto"
},
"filterable": true,
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Estado"
},
"properties": [
{
"id": "mappings",
"value": [
{
"type": "value",
"options": {
"PASS": {
"text": "PASS",
"color": "green",
"index": 0
},
"WARN": {
"text": "WARN",
"color": "orange",
"index": 1
},
"FAIL": {
"text": "FAIL",
"color": "red",
"index": 2
},
"ERROR": {
"text": "ERROR",
"color": "red",
"index": 3
}
}
}
]
},
{
"id": "custom.cellOptions",
"value": {
"type": "color-background"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "Contrato"
},
"properties": [
{
"id": "custom.width",
"value": 420
}
]
},
{
"matcher": {
"id": "byName",
"options": "Detalle"
},
"properties": [
{
"id": "custom.width",
"value": 520
}
]
}
]
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 226
},
"id": 30,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": []
},
"pluginVersion": "11.0.0",
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "bfnbcasbm6hhca"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "SELECT\n test_code AS \"Test\",\n test_group AS \"Grupo\",\n test_title AS \"Contrato\",\n status AS \"Estado\",\n severity AS \"Severidad\",\n detail AS \"Detalle\",\n actual_json::text AS \"Resultado\",\n duration_ms AS \"Duración ms\"\nFROM\n mv_reports_ucepsa_prod\n .li_context_regression_failures_v1\nORDER BY\n CASE status\n WHEN 'ERROR' THEN 1\n WHEN 'FAIL' THEN 2\n ELSE 3\n END,\n test_code",
"refId": "A"
}
],
"title": "Warnings y fallos de regresión",
"type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "bfnbcasbm6hhca"
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"cellOptions": {
"type": "auto"
},
"filterable": true,
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Estado"
},
"properties": [
{
"id": "mappings",
"value": [
{
"type": "value",
"options": {
"PASS": {
"text": "PASS",
"color": "green",
"index": 0
},
"WARN": {
"text": "WARN",
"color": "orange",
"index": 1
},
"FAIL": {
"text": "FAIL",
"color": "red",
"index": 2
},
"ERROR": {
"text": "ERROR",
"color": "red",
"index": 3
}
}
}
]
},
{
"id": "custom.cellOptions",
"value": {
"type": "color-background"
}
}
]
}
]
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 236
},
"id": 31,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": []
},
"pluginVersion": "11.0.0",
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "bfnbcasbm6hhca"
},
"editorMode": "code",
"format": "table",
"rawQuery": true,
"rawSql": "SELECT\n run_id AS \"Ejecución\",\n overall_status AS \"Estado\",\n triggered_by AS \"Disparada por\",\n trigger_kind AS \"Tipo\",\n started_at AS \"Inicio\",\n duration_ms AS \"Duración ms\",\n result_count AS \"Tests\",\n pass_count AS \"PASS\",\n warn_count AS \"WARN\",\n fail_count AS \"FAIL\",\n error_count AS \"ERROR\",\n source_git_commit AS \"Commit\"\nFROM\n mv_reports_ucepsa_prod\n .li_context_regression_run_history_v1\nORDER BY\n started_at DESC,\n run_id DESC\nLIMIT 50",
"refId": "A"
}
],
"title": "Histórico de regresión · 30 días",
"type": "table"
} }
], ],
"refresh": "15s", "refresh": "15s",
@ -3314,7 +3668,11 @@
"daily-governance", "daily-governance",
"agenda", "agenda",
"evidence-pack", "evidence-pack",
"v0312" "v0312",
"regression",
"hardening",
"contracts",
"v0313"
], ],
"templating": { "templating": {
"list": [] "list": []

View File

@ -0,0 +1,259 @@
#!/usr/bin/env bash
set -euo pipefail
ACTION="${1:-deploy}"
GRAFANA_CONTAINER="${GRAFANA_CONTAINER:-mv_ucepsa_grafana}"
REPO_ROOT="${REGRESSION_DASHBOARD_REPO_ROOT:-/srv/mesavault/40-clients/ucepsa/edge-oee-demo}"
DASHBOARD_FILE="${REGRESSION_DASHBOARD_FILE:-$REPO_ROOT/grafana/dashboards/ucepsa-shopfloor-context-health.dashboard.json}"
FOLDER_UID="${REGRESSION_DASHBOARD_FOLDER_UID:-ucepsa-mesavault}"
DASHBOARD_UID="ucepsa-shopfloor-context-health"
DATASOURCE_UID="bfnbcasbm6hhca"
resolve_url() {
if [[ -n "${GRAFANA_URL:-}" ]]; then
printf '%s' "$GRAFANA_URL"
return
fi
local port_line host_port container_ip
port_line="$(
docker port "$GRAFANA_CONTAINER" 3000/tcp 2>/dev/null \
| head -n 1 || true
)"
if [[ -n "$port_line" ]]; then
host_port="${port_line##*:}"
printf 'http://127.0.0.1:%s' "$host_port"
return
fi
container_ip="$(
docker inspect "$GRAFANA_CONTAINER" \
--format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
)"
if [[ -z "$container_ip" ]]; then
echo "ERROR: no se pudo resolver Grafana." >&2
exit 1
fi
printf 'http://%s:3000' "$container_ip"
}
if [[ -z "${GRAFANA_API_TOKEN:-}" ]]; then
echo "ERROR: falta GRAFANA_API_TOKEN." >&2
exit 1
fi
export GRAFANA_URL_RESOLVED
GRAFANA_URL_RESOLVED="$(resolve_url)"
python3 - \
"$ACTION" \
"$DASHBOARD_FILE" \
"$FOLDER_UID" \
"$DASHBOARD_UID" \
"$DATASOURCE_UID" <<'PY'
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
action, dashboard_file, folder_uid, dashboard_uid, datasource_uid = sys.argv[1:]
base_url = os.environ["GRAFANA_URL_RESOLVED"].rstrip("/")
token = os.environ["GRAFANA_API_TOKEN"]
def request(method, path, payload=None):
body = None
headers = {
"Accept": "application/json",
"Authorization": f"Bearer {token}",
}
if payload is not None:
body = json.dumps(
payload,
ensure_ascii=False,
).encode("utf-8")
headers["Content-Type"] = "application/json"
req = urllib.request.Request(
base_url + path,
data=body,
headers=headers,
method=method,
)
try:
with urllib.request.urlopen(
req,
timeout=20,
) as response:
raw = response.read()
return (
json.loads(raw.decode("utf-8"))
if raw
else None
)
except urllib.error.HTTPError as exc:
detail = exc.read().decode(
"utf-8",
errors="replace",
)
raise RuntimeError(
f"Grafana API {method} {path}: "
f"HTTP {exc.code}: {detail}"
) from exc
health = request("GET", "/api/health")
if not health or health.get("database") != "ok":
raise RuntimeError(
f"Grafana no está saludable: {health!r}"
)
if action == "deploy":
with open(
dashboard_file,
encoding="utf-8",
) as handle:
dashboard = json.load(handle)
result = request(
"POST",
"/api/dashboards/db",
{
"dashboard": dashboard,
"folderUid": folder_uid,
"overwrite": True,
"message": (
"MESAVAULT regression hardening "
"SHADOW v0.3.13"
),
},
)
print(json.dumps(
{
"status": "deployed",
"dashboard_uid": dashboard_uid,
"folder_uid": folder_uid,
"response": result,
},
ensure_ascii=False,
indent=2,
))
elif action == "validate":
result = request(
"GET",
"/api/dashboards/uid/"
+ urllib.parse.quote(dashboard_uid),
)
dashboard = result["dashboard"]
panels = {
panel.get("id"): panel
for panel in dashboard.get("panels", [])
}
datasource_uids = sorted({
panel.get("datasource", {}).get("uid")
for panel in dashboard.get("panels", [])
if isinstance(
panel.get("datasource"),
dict,
)
and panel.get(
"datasource",
{},
).get("uid")
})
errors = []
if len(dashboard.get("panels", [])) != 31:
errors.append(
"El dashboard no tiene 31 paneles"
)
if dashboard.get("refresh") != "15s":
errors.append(
"El refresco no está en 15s"
)
if datasource_uids != [datasource_uid]:
errors.append(
f"Datasource inesperado: {datasource_uids!r}"
)
expected = {
29: "li_context_regression_latest_run_v1",
30: "li_context_regression_failures_v1",
31: "li_context_regression_run_history_v1",
}
for panel_id, fragment in expected.items():
sql = (
panels.get(panel_id, {})
.get("targets", [{}])[0]
.get("rawSql", "")
)
if fragment not in sql:
errors.append(
f"Panel {panel_id} no consulta {fragment}"
)
output = {
"status":
"ok" if not errors else "error",
"dashboard_uid":
dashboard.get("uid"),
"folder_uid":
result.get(
"meta",
{},
).get("folderUid"),
"panel_count":
len(
dashboard.get(
"panels",
[],
)
),
"refresh":
dashboard.get("refresh"),
"datasource_uids":
datasource_uids,
"version":
dashboard.get("version"),
"url":
result.get(
"meta",
{},
).get("url"),
"regression_panels": {
"latest_run": 29 in panels,
"issues": 30 in panels,
"history": 31 in panels,
},
"errors":
errors,
}
print(json.dumps(
output,
ensure_ascii=False,
indent=2,
))
if errors:
raise SystemExit(1)
else:
raise SystemExit(
"Uso: deploy_context_regression_dashboard_v0313.sh "
"{deploy|validate}"
)
PY

View File

@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -euo pipefail
ACTION="${1:-show}"
ROOT="${CONTEXT_REGRESSION_RUNTIME_ROOT:-/srv/mesavault/edge-oee-ucepsa}"
REPO="${CONTEXT_REGRESSION_REPO_ROOT:-/srv/mesavault/40-clients}"
NETWORK="${CONTEXT_REGRESSION_NETWORK:-mv_ucepsa_net}"
POSTGRES_CONTAINER="${CONTEXT_REGRESSION_POSTGRES_CONTAINER:-mv_ucepsa_postgres_hot}"
PGHOST_OVERRIDE="${CONTEXT_REGRESSION_PGHOST_OVERRIDE:-mv_ucepsa_postgres_hot}"
cd "$ROOT"
IMAGE="$(
docker inspect \
mv_ucepsa_odoo_order_state_pg_sink \
--format '{{.Config.Image}}'
)"
PGDATABASE_OVERRIDE="${CONTEXT_REGRESSION_PGDATABASE_OVERRIDE:-$(
docker inspect "$POSTGRES_CONTAINER" \
--format '{{range .Config.Env}}{{println .}}{{end}}' \
| sed -n 's/^POSTGRES_DB=//p' \
| head -n 1
)}"
if [[ -z "$PGDATABASE_OVERRIDE" ]]; then
echo "ERROR: no se pudo determinar POSTGRES_DB." >&2
exit 1
fi
SOURCE_GIT_COMMIT="$(
git -C "$REPO" rev-parse HEAD 2>/dev/null \
|| printf 'unknown'
)"
shift || true
docker run --rm -i \
--network "$NETWORK" \
--env-file .env \
-e "PGHOST=$PGHOST_OVERRIDE" \
-e "PGDATABASE=$PGDATABASE_OVERRIDE" \
-e "SOURCE_GIT_COMMIT=$SOURCE_GIT_COMMIT" \
-v "$ROOT:/app:ro" \
-w /app \
--entrypoint python \
"$IMAGE" \
tools/context_regression_suite.py \
"$ACTION" \
"$@"

View File

@ -0,0 +1,217 @@
\pset pager off
\echo '=== 1. Objetos v0.3.13 ==='
SELECT
to_regclass(
'mv_loss_intelligence.context_regression_policies'
) AS policy_table,
to_regclass(
'mv_loss_intelligence.context_regression_runs'
) AS runs_table,
to_regclass(
'mv_loss_intelligence.context_regression_results'
) AS results_table,
to_regclass(
'mv_reports_ucepsa_prod.li_context_regression_latest_run_v1'
) AS latest_run_view,
to_regclass(
'mv_reports_ucepsa_prod.li_context_regression_latest_results_v1'
) AS latest_results_view,
to_regclass(
'mv_reports_ucepsa_prod.li_context_regression_failures_v1'
) AS failures_view,
to_regclass(
'mv_reports_ucepsa_prod.li_context_regression_run_history_v1'
) AS history_view,
to_regclass(
'mv_reports_ucepsa_prod.li_context_regression_health_v1'
) AS health_view;
\echo '=== 2. Última ejecución ==='
SELECT *
FROM
mv_reports_ucepsa_prod
.li_context_regression_latest_run_v1;
\echo '=== 3. Salud de regresión ==='
SELECT *
FROM
mv_reports_ucepsa_prod
.li_context_regression_health_v1;
\echo '=== 4. Conteos de resultados coherentes ==='
SELECT COUNT(*) AS run_count_mismatches
FROM
mv_loss_intelligence.context_regression_runs r
WHERE r.result_count <>
(
SELECT COUNT(*)
FROM
mv_loss_intelligence
.context_regression_results x
WHERE x.run_id =
r.run_id
)
OR r.pass_count <>
(
SELECT COUNT(*)
FROM
mv_loss_intelligence
.context_regression_results x
WHERE x.run_id =
r.run_id
AND x.status = 'PASS'
)
OR r.warn_count <>
(
SELECT COUNT(*)
FROM
mv_loss_intelligence
.context_regression_results x
WHERE x.run_id =
r.run_id
AND x.status = 'WARN'
)
OR r.fail_count <>
(
SELECT COUNT(*)
FROM
mv_loss_intelligence
.context_regression_results x
WHERE x.run_id =
r.run_id
AND x.status = 'FAIL'
)
OR r.error_count <>
(
SELECT COUNT(*)
FROM
mv_loss_intelligence
.context_regression_results x
WHERE x.run_id =
r.run_id
AND x.status = 'ERROR'
);
\echo '=== 5. Códigos de test únicos por ejecución ==='
SELECT COUNT(*) AS duplicate_test_codes
FROM (
SELECT
run_id,
test_code,
COUNT(*) AS n
FROM
mv_loss_intelligence.context_regression_results
GROUP BY
run_id,
test_code
HAVING COUNT(*) > 1
) duplicated;
\echo '=== 6. La última suite está completa ==='
SELECT
l.run_id,
l.overall_status,
l.result_count,
p.minimum_expected_test_count,
(
l.result_count >=
p.minimum_expected_test_count
) AS suite_complete
FROM
mv_reports_ucepsa_prod
.li_context_regression_latest_run_v1 l
JOIN
mv_loss_intelligence.context_regression_policies p
ON p.tenant =
l.tenant
AND p.site =
l.site
AND p.policy_code =
l.policy_code
WHERE p.active;
\echo '=== 7. Fallos o errores críticos en la última suite ==='
SELECT COUNT(*) AS latest_fail_or_error_count
FROM
mv_reports_ucepsa_prod
.li_context_regression_latest_results_v1
WHERE status IN (
'FAIL',
'ERROR'
);
\echo '=== 8. Contratos críticos conocidos ==='
SELECT COUNT(*) AS critical_contract_not_passed
FROM
mv_reports_ucepsa_prod
.li_context_regression_latest_results_v1
WHERE test_code IN (
'CANON-001',
'CANON-002',
'CANON-003',
'CANON-004',
'WO340-001',
'WO340-002',
'LEGACY-001',
'LEGACY-002',
'CONFLICT-001',
'CONFLICT-002',
'CASE-001',
'DECISION-001',
'AGENDA-001',
'SNAPSHOT-001',
'SHADOW-001'
)
AND status <> 'PASS';
\echo '=== 9. Warnings informativos ==='
SELECT
test_code,
test_group,
test_title,
status,
severity,
actual_json,
detail,
duration_ms
FROM
mv_reports_ucepsa_prod
.li_context_regression_failures_v1;
\echo '=== 10. Nada oficial ==='
SELECT
(
SELECT COUNT(*)
FROM
mv_loss_intelligence.context_regression_runs
WHERE official_eligible
) AS official_run_rows,
(
SELECT COUNT(*)
FROM
mv_loss_intelligence.context_regression_results
WHERE official_eligible
) AS official_result_rows,
(
SELECT COUNT(*)
FROM
mv_reports_ucepsa_prod
.li_context_regression_health_v1
WHERE official_eligible
) AS official_health_rows,
(
SELECT COUNT(*)
FROM
mv_reports_ucepsa_prod.li_operator_stop_queue_v2
) AS official_operator_queue_rows;

View File

@ -0,0 +1,501 @@
BEGIN;
CREATE TABLE IF NOT EXISTS
mv_loss_intelligence.context_regression_policies (
tenant text NOT NULL,
site text NOT NULL,
policy_code text NOT NULL,
suite_version text NOT NULL,
stale_after_hours numeric(10, 2) NOT NULL DEFAULT 26
CHECK (stale_after_hours > 0),
performance_warning_ms integer NOT NULL DEFAULT 2000
CHECK (performance_warning_ms > 0),
minimum_expected_test_count integer NOT NULL DEFAULT 20
CHECK (minimum_expected_test_count > 0),
active boolean NOT NULL DEFAULT true,
notes text,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (
tenant,
site,
policy_code
)
);
COMMENT ON TABLE
mv_loss_intelligence.context_regression_policies IS
'Política SHADOW para pruebas de regresión del contexto, arbitraje, gobernanza y guardas oficiales.';
INSERT INTO
mv_loss_intelligence.context_regression_policies (
tenant,
site,
policy_code,
suite_version,
stale_after_hours,
performance_warning_ms,
minimum_expected_test_count,
active,
notes
)
VALUES (
'ucepsa',
'ucepsa_onpremise',
'CONTEXT_REGRESSION_V1',
'0.3.13',
26,
2000,
20,
true,
'La suite protege contratos históricos e invariantes estructurales. Las comprobaciones de salud o rendimiento pueden producir WARNING sin ocultar fallos funcionales.'
)
ON CONFLICT (
tenant,
site,
policy_code
)
DO UPDATE SET
suite_version =
EXCLUDED.suite_version,
stale_after_hours =
EXCLUDED.stale_after_hours,
performance_warning_ms =
EXCLUDED.performance_warning_ms,
minimum_expected_test_count =
EXCLUDED.minimum_expected_test_count,
active = true,
notes =
EXCLUDED.notes,
updated_at = now();
CREATE TABLE IF NOT EXISTS
mv_loss_intelligence.context_regression_runs (
run_id bigserial PRIMARY KEY,
tenant text NOT NULL,
site text NOT NULL,
policy_code text NOT NULL,
suite_version text NOT NULL,
overall_status text NOT NULL
CHECK (
overall_status IN (
'RUNNING',
'PASS',
'WARN',
'FAIL',
'ERROR'
)
),
triggered_by text NOT NULL,
trigger_kind text NOT NULL DEFAULT 'MANUAL'
CHECK (
trigger_kind IN (
'MANUAL',
'DEPLOYMENT',
'SCHEDULED',
'CI'
)
),
started_at timestamptz NOT NULL DEFAULT now(),
finished_at timestamptz,
duration_ms numeric(14, 2),
result_count integer NOT NULL DEFAULT 0
CHECK (result_count >= 0),
pass_count integer NOT NULL DEFAULT 0
CHECK (pass_count >= 0),
warn_count integer NOT NULL DEFAULT 0
CHECK (warn_count >= 0),
fail_count integer NOT NULL DEFAULT 0
CHECK (fail_count >= 0),
skip_count integer NOT NULL DEFAULT 0
CHECK (skip_count >= 0),
error_count integer NOT NULL DEFAULT 0
CHECK (error_count >= 0),
source_git_commit text,
source_state_hash text NOT NULL,
notes text,
official_eligible boolean NOT NULL DEFAULT false
CHECK (official_eligible = false),
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT ck_context_regression_run_time
CHECK (
finished_at IS NULL
OR finished_at >= started_at
)
);
CREATE INDEX IF NOT EXISTS
idx_context_regression_runs_latest
ON
mv_loss_intelligence.context_regression_runs (
tenant,
site,
started_at DESC,
run_id DESC
);
CREATE TABLE IF NOT EXISTS
mv_loss_intelligence.context_regression_results (
result_id bigserial PRIMARY KEY,
run_id bigint NOT NULL
REFERENCES
mv_loss_intelligence.context_regression_runs (
run_id
)
ON DELETE CASCADE,
test_code text NOT NULL,
test_group text NOT NULL,
test_title text NOT NULL,
status text NOT NULL
CHECK (
status IN (
'PASS',
'WARN',
'FAIL',
'SKIP',
'ERROR'
)
),
severity text NOT NULL
CHECK (
severity IN (
'INFO',
'WARNING',
'CRITICAL'
)
),
expected_json jsonb NOT NULL DEFAULT '{}'::jsonb,
actual_json jsonb NOT NULL DEFAULT '{}'::jsonb,
detail text,
duration_ms numeric(14, 2) NOT NULL
CHECK (duration_ms >= 0),
evidence_json jsonb NOT NULL DEFAULT '{}'::jsonb,
observed_at timestamptz NOT NULL DEFAULT now(),
official_eligible boolean NOT NULL DEFAULT false
CHECK (official_eligible = false),
CONSTRAINT ux_context_regression_run_test
UNIQUE (
run_id,
test_code
)
);
CREATE INDEX IF NOT EXISTS
idx_context_regression_results_status
ON
mv_loss_intelligence.context_regression_results (
run_id,
status,
severity,
test_code
);
COMMENT ON TABLE
mv_loss_intelligence.context_regression_runs IS
'Ejecuciones auditadas de la suite de regresión v0.3.13.';
COMMENT ON TABLE
mv_loss_intelligence.context_regression_results IS
'Resultado individual de cada contrato de regresión. No altera datos de producción ni elegibilidad oficial.';
CREATE OR REPLACE VIEW
mv_reports_ucepsa_prod.li_context_regression_latest_run_v1
AS
SELECT DISTINCT ON (
tenant,
site
)
run_id,
tenant,
site,
policy_code,
suite_version,
overall_status,
triggered_by,
trigger_kind,
started_at,
finished_at,
duration_ms,
result_count,
pass_count,
warn_count,
fail_count,
skip_count,
error_count,
source_git_commit,
source_state_hash,
notes,
official_eligible
FROM
mv_loss_intelligence.context_regression_runs
ORDER BY
tenant,
site,
started_at DESC,
run_id DESC;
COMMENT ON VIEW
mv_reports_ucepsa_prod.li_context_regression_latest_run_v1 IS
'Última ejecución de regresión por tenant y site.';
CREATE OR REPLACE VIEW
mv_reports_ucepsa_prod.li_context_regression_latest_results_v1
AS
SELECT
r.run_id,
r.overall_status,
r.started_at,
r.finished_at,
r.source_git_commit,
x.test_code,
x.test_group,
x.test_title,
x.status,
x.severity,
x.expected_json,
x.actual_json,
x.detail,
x.duration_ms,
x.evidence_json,
x.observed_at,
x.official_eligible
FROM
mv_reports_ucepsa_prod
.li_context_regression_latest_run_v1 r
JOIN
mv_loss_intelligence.context_regression_results x
ON x.run_id =
r.run_id;
COMMENT ON VIEW
mv_reports_ucepsa_prod.li_context_regression_latest_results_v1 IS
'Resultados de la última suite de regresión.';
CREATE OR REPLACE VIEW
mv_reports_ucepsa_prod.li_context_regression_failures_v1
AS
SELECT *
FROM
mv_reports_ucepsa_prod
.li_context_regression_latest_results_v1
WHERE status IN (
'WARN',
'FAIL',
'ERROR'
)
ORDER BY
CASE status
WHEN 'ERROR' THEN 1
WHEN 'FAIL' THEN 2
ELSE 3
END,
CASE severity
WHEN 'CRITICAL' THEN 1
WHEN 'WARNING' THEN 2
ELSE 3
END,
test_code;
COMMENT ON VIEW
mv_reports_ucepsa_prod.li_context_regression_failures_v1 IS
'Warnings, fallos y errores de la última ejecución.';
CREATE OR REPLACE VIEW
mv_reports_ucepsa_prod.li_context_regression_run_history_v1
AS
SELECT
run_id,
tenant,
site,
suite_version,
overall_status,
triggered_by,
trigger_kind,
started_at,
finished_at,
duration_ms,
result_count,
pass_count,
warn_count,
fail_count,
skip_count,
error_count,
source_git_commit,
source_state_hash,
notes,
official_eligible
FROM
mv_loss_intelligence.context_regression_runs
WHERE started_at >=
now() - interval '30 days'
ORDER BY
started_at DESC,
run_id DESC;
COMMENT ON VIEW
mv_reports_ucepsa_prod.li_context_regression_run_history_v1 IS
'Histórico de ejecuciones de regresión de los últimos 30 días.';
CREATE OR REPLACE VIEW
mv_reports_ucepsa_prod.li_context_regression_health_v1
AS
WITH policy AS (
SELECT *
FROM
mv_loss_intelligence.context_regression_policies
WHERE tenant = 'ucepsa'
AND site = 'ucepsa_onpremise'
AND policy_code =
'CONTEXT_REGRESSION_V1'
AND active
ORDER BY updated_at DESC
LIMIT 1
),
latest AS (
SELECT *
FROM
mv_reports_ucepsa_prod
.li_context_regression_latest_run_v1
WHERE tenant = 'ucepsa'
AND site = 'ucepsa_onpremise'
)
SELECT
p.tenant,
p.site,
p.policy_code,
p.suite_version
AS configured_suite_version,
p.stale_after_hours,
p.performance_warning_ms,
p.minimum_expected_test_count,
l.run_id,
l.suite_version
AS latest_suite_version,
l.overall_status,
l.started_at,
l.finished_at,
l.result_count,
l.pass_count,
l.warn_count,
l.fail_count,
l.skip_count,
l.error_count,
l.duration_ms,
l.source_git_commit,
CASE
WHEN l.started_at IS NULL
THEN NULL::numeric
ELSE ROUND(
GREATEST(
EXTRACT(
EPOCH FROM (
now()
- l.started_at
)
) / 3600.0,
0
)::numeric,
2
)
END AS hours_since_run,
CASE
WHEN l.run_id IS NULL
THEN 'NOT_INITIALIZED'
WHEN l.overall_status IN (
'FAIL',
'ERROR'
)
THEN 'CRITICAL'
WHEN l.started_at <
now()
- (
p.stale_after_hours
* interval '1 hour'
)
THEN 'STALE'
WHEN l.result_count <
p.minimum_expected_test_count
THEN 'INCOMPLETE'
WHEN l.overall_status =
'WARN'
THEN 'WARNING'
WHEN l.overall_status =
'PASS'
THEN 'HEALTHY'
ELSE 'RUNNING'
END AS regression_health_status,
CASE
WHEN l.run_id IS NULL
OR l.overall_status IN (
'FAIL',
'ERROR'
)
OR l.started_at <
now()
- (
p.stale_after_hours
* interval '1 hour'
)
OR l.result_count <
p.minimum_expected_test_count
THEN 'CRITICAL'
WHEN l.overall_status =
'WARN'
THEN 'WARNING'
WHEN l.overall_status =
'PASS'
THEN 'OK'
ELSE 'INFO'
END AS severity,
false AS official_eligible,
now() AS observed_at
FROM policy p
LEFT JOIN latest l
ON true;
COMMENT ON VIEW
mv_reports_ucepsa_prod.li_context_regression_health_v1 IS
'Salud de la última suite: estado, antigüedad, completitud y versión.';
DO $$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_roles
WHERE rolname =
'grafana_ucepsa_ro'
) THEN
GRANT SELECT
ON
mv_reports_ucepsa_prod
.li_context_regression_latest_run_v1
TO grafana_ucepsa_ro;
GRANT SELECT
ON
mv_reports_ucepsa_prod
.li_context_regression_latest_results_v1
TO grafana_ucepsa_ro;
GRANT SELECT
ON
mv_reports_ucepsa_prod
.li_context_regression_failures_v1
TO grafana_ucepsa_ro;
GRANT SELECT
ON
mv_reports_ucepsa_prod
.li_context_regression_run_history_v1
TO grafana_ucepsa_ro;
GRANT SELECT
ON
mv_reports_ucepsa_prod
.li_context_regression_health_v1
TO grafana_ucepsa_ro;
END IF;
END
$$;
COMMIT;

File diff suppressed because it is too large Load Diff