fix(ucepsa): use persistent conflict evidence in regression suite

This commit is contained in:
Victor Fraile Garcia 2026-07-20 18:44:18 +02:00
parent 3d0aef0410
commit 27a653eb42
3 changed files with 208 additions and 64 deletions

View File

@ -1,4 +1,4 @@
# UCEPSA — Regresión y hardening SHADOW v0.3.13 # UCEPSA — Regresión y hardening SHADOW v0.3.13.1
## Objetivo ## Objetivo
@ -172,3 +172,24 @@ Histórico de regresión
``` ```
Paneles totales esperados: 31. Paneles totales esperados: 31.
## Hotfix 0.3.13.1
La vista `li_context_arbitration_conflicts_v1` es derivada del estado canónico
actual. Al corregir Odoo, el conflicto desaparece correctamente de esa vista.
Los contratos `CONFLICT-001` y `CONFLICT-002` pasan a verificar:
```text
episodios persistentes 7/8/9
caso raíz 6
decisión consolidada del doble arranque
cero elegibilidad oficial
```
No dependen ya de una ventana derivada vacía y dejan de ejecutar la consulta de
aproximadamente 18 segundos.
La migración 116 solo actualiza la versión de política y el mínimo esperado de
tests a 25. La migración 115 permanece inmutable.

View File

@ -0,0 +1,31 @@
BEGIN;
DO $$
DECLARE
affected_rows integer;
BEGIN
UPDATE
mv_loss_intelligence.context_regression_policies
SET
suite_version = '0.3.13.1',
minimum_expected_test_count = 25,
notes =
'Hotfix 0.3.13.1: los contratos del doble inicio 649/698 usan episodios, caso raíz y decisión persistentes. La vista de conflictos canónicos es derivada y puede quedar vacía tras corregir Odoo.',
updated_at = now()
WHERE tenant = 'ucepsa'
AND site = 'ucepsa_onpremise'
AND policy_code =
'CONTEXT_REGRESSION_V1';
GET DIAGNOSTICS
affected_rows = ROW_COUNT;
IF affected_rows <> 1 THEN
RAISE EXCEPTION
'La actualización de CONTEXT_REGRESSION_V1 afectó % filas; se esperaba exactamente 1.',
affected_rows;
END IF;
END
$$;
COMMIT;

View File

@ -18,7 +18,7 @@ import psycopg2
from psycopg2.extras import Json, RealDictCursor from psycopg2.extras import Json, RealDictCursor
VERSION = "0.3.13" VERSION = "0.3.13.1"
POLICY_CODE = "CONTEXT_REGRESSION_V1" POLICY_CODE = "CONTEXT_REGRESSION_V1"
@ -472,79 +472,169 @@ def contract_tests() -> list[TestCase]:
TestCase( TestCase(
code="CONFLICT-001", code="CONFLICT-001",
group="HISTORICAL_CONTRACT", group="HISTORICAL_CONTRACT",
title="Doble inicio 649/698 conservado como conflicto", title="Doble inicio 649/698 conservado en episodios persistentes",
severity="CRITICAL", severity="CRITICAL",
failure_status="FAIL", failure_status="FAIL",
expected={"violations": 0}, expected={"violations": 0},
detail_pass="El solape histórico de WO 649 y 698 sigue documentado.", detail_pass="Los episodios 7/8/9 conservan el doble inicio ya corregido en Odoo.",
detail_fail="No existe el conflicto histórico esperado entre WO 649 y 698.", detail_fail="La evidencia persistente del doble inicio 649/698 ha cambiado o desaparecido.",
sql=r""" sql=r"""
WITH contract AS (
SELECT
COUNT(*) AS episode_count,
COUNT(*) FILTER (
WHERE incident_scope =
'MACHINE'
) AS machine_scope_count,
COUNT(*) FILTER (
WHERE incident_scope =
'SESSION'
) AS session_scope_count,
COUNT(*) FILTER (
WHERE incident_code =
'MULTIPLE_OPEN_SESSIONS'
) AS code_count,
COUNT(*) FILTER (
WHERE episode_status =
'CLOSED'
) AS closed_count,
COUNT(*) FILTER (
WHERE max_severity =
'CRITICAL'
) AS critical_count,
COUNT(*) FILTER (
WHERE NOT official_eligible
) AS shadow_count,
ARRAY_AGG(
DISTINCT odoo_workorder_id
ORDER BY odoo_workorder_id
) FILTER (
WHERE odoo_workorder_id
IS NOT NULL
) AS workorder_ids,
ARRAY_AGG(
DISTINCT production_order
ORDER BY production_order
) FILTER (
WHERE production_order
IS NOT NULL
) AS production_orders,
MIN(first_seen_at)
AS first_seen_at,
MAX(ended_at)
AS ended_at
FROM
mv_reports_ucepsa_prod
.li_shopfloor_context_incident_history_v1
WHERE episode_id IN (
7,
8,
9
)
)
SELECT SELECT
CASE CASE
WHEN COUNT(*) >= 1 WHEN episode_count = 3
AND machine_scope_count = 1
AND session_scope_count = 2
AND code_count = 3
AND closed_count = 3
AND critical_count = 3
AND shadow_count = 3
AND workorder_ids =
ARRAY[649, 698]::bigint[]
AND production_orders =
ARRAY[
'WH/MO/00660',
'WH/MO/00706'
]::text[]
AND first_seen_at =
'2026-07-20 15:43:50.563848+02'
::timestamptz
AND ended_at IS NOT NULL
THEN 0 THEN 0
ELSE 1 ELSE 1
END AS violations, END AS violations,
COUNT(*) AS conflict_rows, *
COALESCE( FROM contract
SUM(segment_duration_s),
0
) AS conflict_seconds
FROM
mv_reports_ucepsa_prod
.li_context_arbitration_conflicts_v1
WHERE machine_id = 'CORT-00'
AND conflict_type =
'MULTIPLE_ODOO_SHOPFLOOR_SESSIONS'
AND authoritative_context_keys @>
ARRAY[
'ODOO_SHOPFLOOR:WORKORDER:649',
'ODOO_SHOPFLOOR:WORKORDER:698'
]::text[]
""", """,
), ),
TestCase( TestCase(
code="CONFLICT-002", code="CONFLICT-002",
group="HISTORICAL_CONTRACT", group="HISTORICAL_CONTRACT",
title="El conflicto 649/698 nunca recibe prioridad canónica", title="Caso y decisión conservan la trazabilidad del doble inicio",
severity="CRITICAL", severity="CRITICAL",
failure_status="FAIL", failure_status="FAIL",
expected={"violations": 0}, expected={"violations": 0},
detail_pass="Todos los tramos del doble inicio permanecen bloqueados.", detail_pass="El caso raíz y la decisión mantienen workorders, órdenes y evidencias sin elegibilidad oficial.",
detail_fail="Algún tramo del conflicto recibió una clave canónica.", detail_fail="La trazabilidad persistente del doble inicio no coincide con el contrato.",
sql=r""" sql=r"""
WITH conflict_window AS ( WITH root_case AS (
SELECT SELECT
MIN(segment_from) AS conflict_from, COUNT(*) AS row_count,
MAX(segment_to) AS conflict_to COUNT(*) FILTER (
FROM WHERE case_family =
mv_reports_ucepsa_prod 'MULTIPLE_OPEN_SESSIONS'
.li_context_arbitration_conflicts_v1 AND machine_id =
WHERE machine_id = 'CORT-00' 'CORT-00'
AND authoritative_context_keys @> AND episode_ids =
ARRAY[7, 8, 9]::bigint[]
AND production_orders =
ARRAY[ ARRAY[
'ODOO_SHOPFLOOR:WORKORDER:649', 'WH/MO/00660',
'ODOO_SHOPFLOOR:WORKORDER:698' 'WH/MO/00706'
]::text[] ]::text[]
) AND odoo_workorder_ids =
SELECT COUNT(*) AS violations ARRAY[649, 698]::bigint[]
AND NOT official_eligible
) AS contract_count
FROM FROM
mv_reports_ucepsa_prod mv_reports_ucepsa_prod
.li_canonical_context_segments_v1 s .li_context_review_case_history_v1
CROSS JOIN conflict_window c WHERE case_id = 6
WHERE c.conflict_from IS NOT NULL ),
AND s.machine_id = 'CORT-00' decision AS (
AND s.segment_to > SELECT
c.conflict_from COUNT(*) AS row_count,
AND s.segment_from < COUNT(*) FILTER (
c.conflict_to WHERE decision_family =
AND ( 'ODOO_ORDER_START_CONFLICT'
s.canonical_context_status <> AND machine_id =
'CONTEXT_CONFLICT' 'CORT-00'
OR s.canonical_context_key AND root_case_ids =
IS NOT NULL ARRAY[5, 6]::bigint[]
OR s.authoritative_candidate_count <= 1 AND production_orders =
ARRAY[
'WH/MO/00660',
'WH/MO/00706'
]::text[]
AND odoo_workorder_ids =
ARRAY[649, 698]::bigint[]
AND NOT official_eligible
) AS contract_count
FROM
mv_reports_ucepsa_prod
.li_context_decision_group_history_v1
WHERE decision_group_key =
'DECISION:ODOO_ORDER_START_CONFLICT:CORT-00:20260720:5'
) )
SELECT
CASE
WHEN r.row_count = 1
AND r.contract_count = 1
AND d.row_count = 1
AND d.contract_count = 1
THEN 0
ELSE 1
END AS violations,
r.row_count AS root_case_rows,
r.contract_count
AS root_case_contract_rows,
d.row_count AS decision_rows,
d.contract_count
AS decision_contract_rows
FROM root_case r
CROSS JOIN decision d
""", """,
), ),
TestCase( TestCase(
@ -793,11 +883,8 @@ def contract_tests() -> list[TestCase]:
detail_pass="Las cabeceras y los elementos de snapshot coinciden.", detail_pass="Las cabeceras y los elementos de snapshot coinciden.",
detail_fail="Un snapshot declara un número de elementos incorrecto.", detail_fail="Un snapshot declara un número de elementos incorrecto.",
sql=r""" sql=r"""
SELECT COUNT(*) AS violations, SELECT
COUNT(*) AS snapshot_count COUNT(*) FILTER (
FROM
mv_loss_intelligence
.context_governance_agenda_snapshots s
WHERE s.item_count <> WHERE s.item_count <>
( (
SELECT COUNT(*) SELECT COUNT(*)
@ -807,6 +894,11 @@ def contract_tests() -> list[TestCase]:
WHERE i.snapshot_id = WHERE i.snapshot_id =
s.snapshot_id s.snapshot_id
) )
) AS violations,
COUNT(*) AS total_snapshot_count
FROM
mv_loss_intelligence
.context_governance_agenda_snapshots s
""", """,
), ),
TestCase( TestCase(