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
|
||||
|
||||
@ -172,3 +172,24 @@ Histórico de regresión
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
|
||||
VERSION = "0.3.13"
|
||||
VERSION = "0.3.13.1"
|
||||
POLICY_CODE = "CONTEXT_REGRESSION_V1"
|
||||
|
||||
|
||||
@ -472,79 +472,169 @@ def contract_tests() -> list[TestCase]:
|
||||
TestCase(
|
||||
code="CONFLICT-001",
|
||||
group="HISTORICAL_CONTRACT",
|
||||
title="Doble inicio 649/698 conservado como conflicto",
|
||||
title="Doble inicio 649/698 conservado en episodios persistentes",
|
||||
severity="CRITICAL",
|
||||
failure_status="FAIL",
|
||||
expected={"violations": 0},
|
||||
detail_pass="El solape histórico de WO 649 y 698 sigue documentado.",
|
||||
detail_fail="No existe el conflicto histórico esperado entre WO 649 y 698.",
|
||||
detail_pass="Los episodios 7/8/9 conservan el doble inicio ya corregido en Odoo.",
|
||||
detail_fail="La evidencia persistente del doble inicio 649/698 ha cambiado o desaparecido.",
|
||||
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
|
||||
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
|
||||
ELSE 1
|
||||
END AS violations,
|
||||
COUNT(*) AS conflict_rows,
|
||||
COALESCE(
|
||||
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[]
|
||||
*
|
||||
FROM contract
|
||||
""",
|
||||
),
|
||||
TestCase(
|
||||
code="CONFLICT-002",
|
||||
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",
|
||||
failure_status="FAIL",
|
||||
expected={"violations": 0},
|
||||
detail_pass="Todos los tramos del doble inicio permanecen bloqueados.",
|
||||
detail_fail="Algún tramo del conflicto recibió una clave canónica.",
|
||||
detail_pass="El caso raíz y la decisión mantienen workorders, órdenes y evidencias sin elegibilidad oficial.",
|
||||
detail_fail="La trazabilidad persistente del doble inicio no coincide con el contrato.",
|
||||
sql=r"""
|
||||
WITH conflict_window AS (
|
||||
WITH root_case AS (
|
||||
SELECT
|
||||
MIN(segment_from) AS conflict_from,
|
||||
MAX(segment_to) AS conflict_to
|
||||
COUNT(*) AS row_count,
|
||||
COUNT(*) FILTER (
|
||||
WHERE case_family =
|
||||
'MULTIPLE_OPEN_SESSIONS'
|
||||
AND machine_id =
|
||||
'CORT-00'
|
||||
AND episode_ids =
|
||||
ARRAY[7, 8, 9]::bigint[]
|
||||
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_arbitration_conflicts_v1
|
||||
WHERE machine_id = 'CORT-00'
|
||||
AND authoritative_context_keys @>
|
||||
ARRAY[
|
||||
'ODOO_SHOPFLOOR:WORKORDER:649',
|
||||
'ODOO_SHOPFLOOR:WORKORDER:698'
|
||||
]::text[]
|
||||
.li_context_review_case_history_v1
|
||||
WHERE case_id = 6
|
||||
),
|
||||
decision AS (
|
||||
SELECT
|
||||
COUNT(*) AS row_count,
|
||||
COUNT(*) FILTER (
|
||||
WHERE decision_family =
|
||||
'ODOO_ORDER_START_CONFLICT'
|
||||
AND machine_id =
|
||||
'CORT-00'
|
||||
AND root_case_ids =
|
||||
ARRAY[5, 6]::bigint[]
|
||||
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 COUNT(*) AS violations
|
||||
FROM
|
||||
mv_reports_ucepsa_prod
|
||||
.li_canonical_context_segments_v1 s
|
||||
CROSS JOIN conflict_window c
|
||||
WHERE c.conflict_from IS NOT NULL
|
||||
AND s.machine_id = 'CORT-00'
|
||||
AND s.segment_to >
|
||||
c.conflict_from
|
||||
AND s.segment_from <
|
||||
c.conflict_to
|
||||
AND (
|
||||
s.canonical_context_status <>
|
||||
'CONTEXT_CONFLICT'
|
||||
OR s.canonical_context_key
|
||||
IS NOT NULL
|
||||
OR s.authoritative_candidate_count <= 1
|
||||
)
|
||||
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(
|
||||
@ -793,20 +883,22 @@ def contract_tests() -> list[TestCase]:
|
||||
detail_pass="Las cabeceras y los elementos de snapshot coinciden.",
|
||||
detail_fail="Un snapshot declara un número de elementos incorrecto.",
|
||||
sql=r"""
|
||||
SELECT COUNT(*) AS violations,
|
||||
COUNT(*) AS snapshot_count
|
||||
SELECT
|
||||
COUNT(*) FILTER (
|
||||
WHERE s.item_count <>
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM
|
||||
mv_loss_intelligence
|
||||
.context_governance_agenda_snapshot_items i
|
||||
WHERE i.snapshot_id =
|
||||
s.snapshot_id
|
||||
)
|
||||
) AS violations,
|
||||
COUNT(*) AS total_snapshot_count
|
||||
FROM
|
||||
mv_loss_intelligence
|
||||
.context_governance_agenda_snapshots s
|
||||
WHERE s.item_count <>
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM
|
||||
mv_loss_intelligence
|
||||
.context_governance_agenda_snapshot_items i
|
||||
WHERE i.snapshot_id =
|
||||
s.snapshot_id
|
||||
)
|
||||
""",
|
||||
),
|
||||
TestCase(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user