fix(ucepsa): use persistent conflict evidence in regression suite
This commit is contained in:
parent
3d0aef0410
commit
27a653eb42
@ -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.
|
||||||
|
|||||||
@ -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;
|
||||||
@ -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(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user