F5.1: adaptar ensayo de migraciones al inicio limpio
This commit is contained in:
+64
-63
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user