F5.1: adaptar ensayo de migraciones al inicio limpio

This commit is contained in:
2026-09-09 08:03:13 -03:00
parent 1e486a80ea
commit 2c615e405d
+64 -63
View File
@@ -80,12 +80,9 @@ jobs:
docker compose --env-file .env.example up -d db docker compose --env-file .env.example up -d db
# The historical production reset is a one-shot operational migration, # Historical production reset is a one-shot migration that expects the
# not a bootstrap migration: it requires production data/configuration # production admin. Prove the clean chain reaches that exact guard,
# that cannot exist at its timestamp in a database rebuilt from zero. # mark only that historical reset as applied, then continue the chain.
# 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.
bootstrap_log="$(mktemp)" bootstrap_log="$(mktemp)"
set +e set +e
docker compose --env-file .env.example --profile tools run --build --rm migrate 2>&1 | tee "$bootstrap_log" 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 \ docker compose --env-file .env.example exec -T db \
psql -v ON_ERROR_STOP=1 -U dhv2_owner -d dhv2 <<'SQL' psql -v ON_ERROR_STOP=1 -U dhv2_owner -d dhv2 <<'SQL'
DO $$ DO $$
DECLARE DECLARE reset_rows integer;
reset_rows integer;
BEGIN BEGIN
SELECT COUNT(*) INTO reset_rows SELECT COUNT(*) INTO reset_rows
FROM typeorm_migrations FROM typeorm_migrations
WHERE name = 'ResetProductionOperationalData1788652800000'; WHERE name = 'ResetProductionOperationalData1788652800000';
IF reset_rows <> 0 THEN IF reset_rows <> 0 THEN
RAISE EXCEPTION 'CI one-shot bypass expected reset migration to be pending, found % rows', reset_rows; RAISE EXCEPTION 'CI one-shot bypass expected reset migration to be pending, found % rows', reset_rows;
END IF; END IF;
INSERT INTO typeorm_migrations ("timestamp", name) INSERT INTO typeorm_migrations ("timestamp", name)
VALUES (1788652800000, 'ResetProductionOperationalData1788652800000'); VALUES (1788652800000, 'ResetProductionOperationalData1788652800000');
END $$; END $$;
@@ -122,17 +116,23 @@ jobs:
docker compose --env-file .env.example --profile tools run --rm migrate 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 \ docker compose --env-file .env.example exec -T db \
psql -v ON_ERROR_STOP=1 -U dhv2_owner -d dhv2 <<'SQL' psql -v ON_ERROR_STOP=1 -U dhv2_owner -d dhv2 <<'SQL'
DO $$ DO $$
DECLARE DECLARE
f5_migrations integer; f5_migrations integer;
real_inventory integer; domain_assets integer;
source_areas integer; audits integer;
source_yacimientos integer; applicability integer;
territory_sources integer;
source_installations integer; source_installations integer;
source_subinstallations integer; source_subinstallations integer;
source_findings integer; source_findings integer;
department_types integer;
area_department_rules integer;
BEGIN BEGIN
SELECT COUNT(*) INTO f5_migrations SELECT COUNT(*) INTO f5_migrations
FROM typeorm_migrations FROM typeorm_migrations
@@ -141,28 +141,33 @@ jobs:
'F5CanonicalInventoryHierarchy1790087150000', 'F5CanonicalInventoryHierarchy1790087150000',
'F5AuthoritativeTerritory1790087200000', 'F5AuthoritativeTerritory1790087200000',
'F5OperationalContextCompatibility1790087250000', 'F5OperationalContextCompatibility1790087250000',
'F5AuthoritativeInventoryCatalog1790087300000' 'F5AuthoritativeInventoryCatalog1790087300000',
'F51CleanManualInventory1790087400000'
); );
IF f5_migrations <> 5 THEN IF f5_migrations <> 6 THEN
RAISE EXCEPTION 'Expected 5 F5 migrations, got %', f5_migrations; RAISE EXCEPTION 'Expected 6 F5/F5.1 migrations, got %', f5_migrations;
END IF; END IF;
SELECT COUNT(*) INTO real_inventory SELECT COUNT(*) INTO domain_assets FROM assets;
FROM assets WHERE is_inventory_instance=true AND information_status<>'INACTIVE'; IF domain_assets <> 0 THEN
IF real_inventory <> 0 THEN RAISE EXCEPTION 'F5.1 clean start must contain 0 Assets, got %', domain_assets;
RAISE EXCEPTION 'Fresh F5 database must start with 0 real Inventory instances, got %', real_inventory;
END IF; END IF;
SELECT COUNT(DISTINCT asset.id) FILTER (WHERE type.operational_role='AREA'), SELECT COUNT(*) INTO audits FROM audit_events;
COUNT(DISTINCT asset.id) FILTER (WHERE lower(type.code)='yacimiento') IF audits <> 0 THEN
INTO source_areas,source_yacimientos RAISE EXCEPTION 'F5.1 clean start must contain 0 audit events, got %', audits;
FROM source_documents document END IF;
JOIN asset_source_documents link ON link.document_id=document.id
JOIN assets asset ON asset.id=link.asset_id SELECT COUNT(*) INTO applicability FROM finding_catalog_item_inventory_families;
JOIN asset_types type ON type.id=asset.asset_type_id IF applicability <> 0 THEN
WHERE document.document_number='DH-F5-TERRITORY'; RAISE EXCEPTION 'F5.1 clean start must contain 0 finding applicability links, got %', applicability;
IF source_areas <> 64 OR source_yacimientos <> 230 THEN END IF;
RAISE EXCEPTION 'F5 territory preload mismatch: areas %, yacimientos %', source_areas,source_yacimientos;
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; END IF;
SELECT COUNT(*) FILTER (WHERE level='INSTALLATION'), SELECT COUNT(*) FILTER (WHERE level='INSTALLATION'),
@@ -171,7 +176,7 @@ jobs:
FROM inventory_families FROM inventory_families
WHERE is_active=true AND source_reference LIKE 'F5:final_modelov2.xlsx%'; WHERE is_active=true AND source_reference LIKE 'F5:final_modelov2.xlsx%';
IF source_installations <> 14 OR source_subinstallations <> 109 THEN 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; END IF;
SELECT COUNT(*) INTO source_findings SELECT COUNT(*) INTO source_findings
@@ -179,42 +184,38 @@ jobs:
JOIN finding_categories category ON category.id=item.category_id JOIN finding_categories category ON category.id=item.category_id
WHERE lower(category.code)='f5model' AND item.is_active=true; WHERE lower(category.code)='f5model' AND item.is_active=true;
IF source_findings <> 177 THEN 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 IF;
END $$; END $$;
SQL SQL
# Prove the five F5 migrations are actually reversible on a clean state. # F5.1 is intentionally one-way: production rollback is the PRE database
for _ in 1 2 3 4 5; do # backup, not migration:revert. Prove instead that the completed chain is
docker compose --env-file .env.example --profile tools run --rm migrate npm run migration:revert # idempotent and has no pending migration on a second run.
done rerun_log="$(mktemp)"
docker compose --env-file .env.example --profile tools run --rm migrate 2>&1 | tee "$rerun_log"
docker compose --env-file .env.example exec -T db \ grep -Eq 'No pending migrations|Applied migrations: 0' "$rerun_log" || {
psql -v ON_ERROR_STOP=1 -U dhv2_owner -d dhv2 <<'SQL' echo "ERROR: F5.1 migration chain is not idempotent." >&2
DO $$ cat "$rerun_log" >&2
DECLARE f5_migrations integer; instance_column integer; exit 1
BEGIN }
SELECT COUNT(*) INTO f5_migrations rm -f "$rerun_log"
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'
- name: VPS-equivalent isolated API preflight - name: VPS-equivalent isolated API preflight
run: | run: |
set -Eeuo pipefail set -Eeuo pipefail