F5.1 · Inventario limpio, jerarquía manual y Hallazgos filtrados (#28)

Reset controlado de datos operativos e históricos, nueva jerarquía manual Departamento → Área → Yacimiento → Instalación → Subinstalación, filtros y administración inline de Hallazgos, y corrección de alta con Área sin Operadora.
This commit is contained in:
2026-09-09 08:55:01 -03:00
committed by GitHub
parent 3d83545033
commit 7338f277ad
16 changed files with 816 additions and 498 deletions
+64 -63
View File
@@ -80,12 +80,9 @@ jobs:
docker compose --env-file .env.example up -d db
# The historical production reset is a one-shot operational migration,
# not a bootstrap migration: it requires production data/configuration
# that cannot exist at its timestamp in a database rebuilt from zero.
# Prove the clean chain reaches that exact guard, then mark only that
# one-shot migration as already applied and continue the reproducible
# schema chain. The historical migration itself remains untouched.
# Historical production reset is a one-shot migration that expects the
# production admin. Prove the clean chain reaches that exact guard,
# mark only that historical reset as applied, then continue the chain.
bootstrap_log="$(mktemp)"
set +e
docker compose --env-file .env.example --profile tools run --build --rm migrate 2>&1 | tee "$bootstrap_log"
@@ -104,17 +101,14 @@ jobs:
docker compose --env-file .env.example exec -T db \
psql -v ON_ERROR_STOP=1 -U dhv2_owner -d dhv2 <<'SQL'
DO $$
DECLARE
reset_rows integer;
DECLARE reset_rows integer;
BEGIN
SELECT COUNT(*) INTO reset_rows
FROM typeorm_migrations
WHERE name = 'ResetProductionOperationalData1788652800000';
IF reset_rows <> 0 THEN
RAISE EXCEPTION 'CI one-shot bypass expected reset migration to be pending, found % rows', reset_rows;
END IF;
INSERT INTO typeorm_migrations ("timestamp", name)
VALUES (1788652800000, 'ResetProductionOperationalData1788652800000');
END $$;
@@ -122,17 +116,23 @@ jobs:
docker compose --env-file .env.example --profile tools run --rm migrate
# F5.1 intentionally ends with zero operational/domain instances. The
# technical family and finding masters remain, but territory preload,
# imports, applicability links and old audits are deliberately gone.
docker compose --env-file .env.example exec -T db \
psql -v ON_ERROR_STOP=1 -U dhv2_owner -d dhv2 <<'SQL'
DO $$
DECLARE
f5_migrations integer;
real_inventory integer;
source_areas integer;
source_yacimientos integer;
domain_assets integer;
audits integer;
applicability integer;
territory_sources integer;
source_installations integer;
source_subinstallations integer;
source_findings integer;
department_types integer;
area_department_rules integer;
BEGIN
SELECT COUNT(*) INTO f5_migrations
FROM typeorm_migrations
@@ -141,28 +141,33 @@ jobs:
'F5CanonicalInventoryHierarchy1790087150000',
'F5AuthoritativeTerritory1790087200000',
'F5OperationalContextCompatibility1790087250000',
'F5AuthoritativeInventoryCatalog1790087300000'
'F5AuthoritativeInventoryCatalog1790087300000',
'F51CleanManualInventory1790087400000'
);
IF f5_migrations <> 5 THEN
RAISE EXCEPTION 'Expected 5 F5 migrations, got %', f5_migrations;
IF f5_migrations <> 6 THEN
RAISE EXCEPTION 'Expected 6 F5/F5.1 migrations, got %', f5_migrations;
END IF;
SELECT COUNT(*) INTO real_inventory
FROM assets WHERE is_inventory_instance=true AND information_status<>'INACTIVE';
IF real_inventory <> 0 THEN
RAISE EXCEPTION 'Fresh F5 database must start with 0 real Inventory instances, got %', real_inventory;
SELECT COUNT(*) INTO domain_assets FROM assets;
IF domain_assets <> 0 THEN
RAISE EXCEPTION 'F5.1 clean start must contain 0 Assets, got %', domain_assets;
END IF;
SELECT COUNT(DISTINCT asset.id) FILTER (WHERE type.operational_role='AREA'),
COUNT(DISTINCT asset.id) FILTER (WHERE lower(type.code)='yacimiento')
INTO source_areas,source_yacimientos
FROM source_documents document
JOIN asset_source_documents link ON link.document_id=document.id
JOIN assets asset ON asset.id=link.asset_id
JOIN asset_types type ON type.id=asset.asset_type_id
WHERE document.document_number='DH-F5-TERRITORY';
IF source_areas <> 64 OR source_yacimientos <> 230 THEN
RAISE EXCEPTION 'F5 territory preload mismatch: areas %, yacimientos %', source_areas,source_yacimientos;
SELECT COUNT(*) INTO audits FROM audit_events;
IF audits <> 0 THEN
RAISE EXCEPTION 'F5.1 clean start must contain 0 audit events, got %', audits;
END IF;
SELECT COUNT(*) INTO applicability FROM finding_catalog_item_inventory_families;
IF applicability <> 0 THEN
RAISE EXCEPTION 'F5.1 clean start must contain 0 finding applicability links, got %', applicability;
END IF;
SELECT COUNT(*) INTO 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;
END IF;
SELECT COUNT(*) FILTER (WHERE level='INSTALLATION'),
@@ -171,7 +176,7 @@ jobs:
FROM inventory_families
WHERE is_active=true AND source_reference LIKE 'F5:final_modelov2.xlsx%';
IF source_installations <> 14 OR source_subinstallations <> 109 THEN
RAISE EXCEPTION 'F5 family preload mismatch: installations %, subinstallations %', source_installations,source_subinstallations;
RAISE EXCEPTION 'F5.1 must preserve technical family masters: installations %, subinstallations %', source_installations,source_subinstallations;
END IF;
SELECT COUNT(*) INTO source_findings
@@ -179,42 +184,38 @@ jobs:
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 'F5 finding preload mismatch: %', source_findings;
RAISE EXCEPTION 'F5.1 must preserve finding master catalog, got %', source_findings;
END IF;
SELECT COUNT(*) INTO department_types
FROM asset_types
WHERE lower(code)='departamento' AND can_be_root=true AND is_active=true;
IF department_types <> 1 THEN
RAISE EXCEPTION 'Expected one active root Departamento type, got %', department_types;
END IF;
SELECT COUNT(*) INTO area_department_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)='area' AND lower(parent.code)='departamento';
IF area_department_rules <> 1 THEN
RAISE EXCEPTION 'Expected Area → Departamento canonical rule, got %', area_department_rules;
END IF;
END $$;
SQL
# Prove the five F5 migrations are actually reversible on a clean state.
for _ in 1 2 3 4 5; do
docker compose --env-file .env.example --profile tools run --rm migrate npm run migration:revert
done
docker compose --env-file .env.example exec -T db \
psql -v ON_ERROR_STOP=1 -U dhv2_owner -d dhv2 <<'SQL'
DO $$
DECLARE f5_migrations integer; instance_column integer;
BEGIN
SELECT COUNT(*) INTO f5_migrations
FROM typeorm_migrations
WHERE name LIKE 'F5%1790087%';
IF f5_migrations <> 0 THEN
RAISE EXCEPTION 'F5 rollback left % migration rows behind', f5_migrations;
END IF;
SELECT COUNT(*) INTO instance_column
FROM information_schema.columns
WHERE table_schema='public' AND table_name='assets' AND column_name='is_inventory_instance';
IF instance_column <> 0 THEN
RAISE EXCEPTION 'F5 rollback left is_inventory_instance behind';
END IF;
END $$;
SQL
# Reapply them once more. Each F5 migration performs its own source/cardinality checks.
docker compose --env-file .env.example --profile tools run --rm migrate
docker compose --env-file .env.example exec -T db \
psql -v ON_ERROR_STOP=1 -U dhv2_owner -d dhv2 -Atc \
"SELECT CASE WHEN COUNT(*)=5 THEN 'F5_REAPPLY_OK' ELSE 'F5_REAPPLY_FAILED:'||COUNT(*) END FROM typeorm_migrations WHERE name IN ('F5InventoryPhysicalInstance1790087100000','F5CanonicalInventoryHierarchy1790087150000','F5AuthoritativeTerritory1790087200000','F5OperationalContextCompatibility1790087250000','F5AuthoritativeInventoryCatalog1790087300000');" \
| grep -Fx 'F5_REAPPLY_OK'
# F5.1 is intentionally one-way: production rollback is the PRE database
# backup, not migration:revert. Prove instead that 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: F5.1 migration chain is not idempotent." >&2
cat "$rerun_log" >&2
exit 1
}
rm -f "$rerun_log"
- name: VPS-equivalent isolated API preflight
run: |
set -Eeuo pipefail