ci: validate authoritative inventory model
This commit is contained in:
+100
-25
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user