From 5986474a3537126f9fcde0ffb02e15dacef9c09b Mon Sep 17 00:00:00 2001 From: enlineawork Date: Fri, 11 Sep 2026 01:28:46 -0300 Subject: [PATCH] ci: validate authoritative inventory model --- .github/workflows/ci.yml | 125 +++++++++++++++++++++++++++++++-------- 1 file changed, 100 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index daaec8a..b79b05e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,9 +116,10 @@ jobs: docker compose --env-file .env.example --profile tools run --rm migrate - # F5.1 performs the historical clean start. F6.1 then seeds only the - # presentation territory approved by DH: 7 Departamentos, 64 Áreas, - # 230 Yacimientos and 12 real Empresas. + # The completed chain must end in the exact authoritative SQL model: + # 7 Departamentos, 64 Áreas, 230 Yacimientos, 13 Empresas, 2 Tipos de + # concesión, 14/109 technical families, 181 Hallazgos and their exact + # 48 + 880 contextual mappings. docker compose --env-file .env.example exec -T db \ psql -v ON_ERROR_STOP=1 -U dhv2_owner -d dhv2 <<'SQL' DO $$ @@ -127,13 +128,23 @@ jobs: domain_assets integer; audits integer; applicability integer; - territory_sources integer; - presentation_sources integer; + legacy_territory_sources integer; + legacy_presentation_sources integer; + authoritative_sources integer; source_installations integer; source_subinstallations integer; source_findings integer; + concession_types integer; + departments integer; + areas integer; + yacimientos integer; + companies integer; + invalid_yacimientos integer; department_types integer; area_department_rules integer; + yacimiento_area_rules integer; + installation_yacimiento_rules integer; + subinstallation_installation_rules integer; BEGIN SELECT COUNT(*) INTO f5_migrations FROM typeorm_migrations @@ -150,49 +161,86 @@ jobs: END IF; SELECT COUNT(*) INTO domain_assets FROM assets; - IF domain_assets <> 313 THEN - RAISE EXCEPTION 'F6.1 presentation preload must contain 313 Assets, got %', domain_assets; + IF domain_assets <> 314 THEN + RAISE EXCEPTION 'Authoritative model must contain 314 Assets, got %', domain_assets; END IF; SELECT COUNT(*) INTO audits FROM audit_events; IF audits <> 0 THEN - RAISE EXCEPTION 'F6.1 presentation preload must start with 0 audit events, got %', audits; + RAISE EXCEPTION 'Clean authoritative rehearsal must start with 0 audit events, got %', audits; END IF; SELECT COUNT(*) INTO applicability FROM finding_catalog_item_inventory_families; - IF applicability <> 0 THEN - RAISE EXCEPTION 'Clean migration rehearsal must contain 0 finding applicability links, got %', applicability; + IF applicability <> 928 THEN + RAISE EXCEPTION 'Authoritative model must contain 928 finding applicability links, got %', applicability; END IF; - SELECT COUNT(*) INTO territory_sources + SELECT COUNT(*) INTO legacy_territory_sources FROM source_documents WHERE document_number='DH-F5-TERRITORY'; - IF territory_sources <> 0 THEN - RAISE EXCEPTION 'F5.1 must remove the old territory source preload, got % rows', territory_sources; + IF legacy_territory_sources <> 0 THEN + RAISE EXCEPTION 'Authoritative model must remove the old F5 territory source, got % rows', legacy_territory_sources; END IF; - SELECT COUNT(*) INTO presentation_sources + SELECT COUNT(*) INTO legacy_presentation_sources FROM source_documents WHERE document_number='DH-F6.1-PRESENTATION-TERRITORY-20260909'; - IF presentation_sources <> 1 THEN - RAISE EXCEPTION 'F6.1 must contain exactly one presentation territory source, got %', presentation_sources; + IF legacy_presentation_sources <> 0 THEN + RAISE EXCEPTION 'Authoritative model must remove the F6.1 presentation source, got % rows', legacy_presentation_sources; + END IF; + + SELECT COUNT(*) INTO authoritative_sources + FROM source_documents + WHERE document_number IN ('DH-AUTH-TERRITORY-20260911','DH-AUTH-INVENTORY-20260911'); + IF authoritative_sources <> 2 THEN + RAISE EXCEPTION 'Authoritative model must contain its two SQL source documents, got %', authoritative_sources; END IF; SELECT COUNT(*) FILTER (WHERE level='INSTALLATION'), COUNT(*) FILTER (WHERE level='SUBINSTALLATION') INTO source_installations,source_subinstallations FROM inventory_families - WHERE is_active=true AND source_reference LIKE 'F5:final_modelov2.xlsx%'; + WHERE is_active=true AND source_reference LIKE 'modelo_relacional:%'; IF source_installations <> 14 OR source_subinstallations <> 109 THEN - RAISE EXCEPTION 'F6.1 must preserve technical family masters: installations %, subinstallations %', source_installations,source_subinstallations; + RAISE EXCEPTION 'Authoritative technical families must be 14 installations and 109 subinstallations, got % and %', source_installations,source_subinstallations; END IF; SELECT COUNT(*) INTO source_findings FROM finding_catalog_items item JOIN finding_categories category ON category.id=item.category_id - WHERE lower(category.code)='f5model' AND item.is_active=true; - IF source_findings <> 177 THEN - RAISE EXCEPTION 'F6.1 must preserve finding master catalog, got %', source_findings; + WHERE lower(category.code)='authmodel' AND item.is_active=true; + IF source_findings <> 181 THEN + RAISE EXCEPTION 'Authoritative finding catalog must contain 181 items, got %', source_findings; + END IF; + + SELECT COUNT(*) INTO concession_types FROM concession_types WHERE is_active=true; + IF concession_types <> 2 THEN + RAISE EXCEPTION 'Authoritative model must contain 2 concession types, got %', concession_types; + END IF; + + SELECT COUNT(*) FILTER (WHERE lower(type.code)='departamento'), + COUNT(*) FILTER (WHERE lower(type.code)='area'), + COUNT(*) FILTER (WHERE lower(type.code)='yacimiento'), + COUNT(*) FILTER (WHERE type.operational_role='COMPANY') + INTO departments,areas,yacimientos,companies + FROM assets asset + JOIN asset_types type ON type.id=asset.asset_type_id; + IF departments <> 7 OR areas <> 64 OR yacimientos <> 230 OR companies <> 13 THEN + RAISE EXCEPTION 'Authoritative territory mismatch: departments %, areas %, yacimientos %, companies %', departments,areas,yacimientos,companies; + END IF; + + SELECT COUNT(*) INTO invalid_yacimientos + FROM assets yacimiento + JOIN asset_types type ON type.id=yacimiento.asset_type_id + JOIN assets area ON area.id=yacimiento.parent_id + WHERE lower(type.code)='yacimiento' + AND ( + yacimiento.operational_area_id IS DISTINCT FROM area.id + OR yacimiento.operator_company_id IS NULL + OR yacimiento.concession_type_id IS NULL + ); + IF invalid_yacimientos <> 0 THEN + RAISE EXCEPTION 'Authoritative model contains % invalid Yacimiento relationships', invalid_yacimientos; END IF; SELECT COUNT(*) INTO department_types @@ -210,16 +258,43 @@ jobs: IF area_department_rules <> 1 THEN RAISE EXCEPTION 'Expected Area → Departamento canonical rule, got %', area_department_rules; END IF; + + SELECT COUNT(*) INTO yacimiento_area_rules + FROM asset_type_parent_rules rule + JOIN asset_types child ON child.id=rule.child_type_id + JOIN asset_types parent ON parent.id=rule.parent_type_id + WHERE lower(child.code)='yacimiento' AND lower(parent.code)='area'; + IF yacimiento_area_rules <> 1 THEN + RAISE EXCEPTION 'Expected Yacimiento → Área canonical rule, got %', yacimiento_area_rules; + END IF; + + SELECT COUNT(*) INTO installation_yacimiento_rules + FROM asset_type_parent_rules rule + JOIN asset_types child ON child.id=rule.child_type_id + JOIN asset_types parent ON parent.id=rule.parent_type_id + WHERE lower(child.code)='instalacion' AND lower(parent.code)='yacimiento'; + IF installation_yacimiento_rules <> 1 THEN + RAISE EXCEPTION 'Expected Instalación → Yacimiento canonical rule, got %', installation_yacimiento_rules; + END IF; + + SELECT COUNT(*) INTO subinstallation_installation_rules + FROM asset_type_parent_rules rule + JOIN asset_types child ON child.id=rule.child_type_id + JOIN asset_types parent ON parent.id=rule.parent_type_id + WHERE lower(child.code)='subinstalacion' AND lower(parent.code)='instalacion'; + IF subinstallation_installation_rules <> 1 THEN + RAISE EXCEPTION 'Expected Subinstalación → Instalación canonical rule, got %', subinstallation_installation_rules; + END IF; END $$; SQL - # Both destructive cuts are intentionally one-way: production rollback is - # the PRE database backup, not migration:revert. Prove the completed chain - # is idempotent and has no pending migration on a second run. + # Destructive cuts are intentionally one-way: production rollback is + # the PRE database backup, not migration:revert. Prove the completed + # chain is idempotent and has no pending migration on a second run. rerun_log="$(mktemp)" docker compose --env-file .env.example --profile tools run --rm migrate 2>&1 | tee "$rerun_log" grep -Eq 'No pending migrations|Applied migrations: 0' "$rerun_log" || { - echo "ERROR: F6.1 migration chain is not idempotent." >&2 + echo "ERROR: authoritative migration chain is not idempotent." >&2 cat "$rerun_log" >&2 exit 1 }