* fix(web): simplify inventory administration menu * fix(web): remove legacy imports and function catalog routes * fix(web): remove redundant inspections lifecycle legend * feat(inventory): distinguish physical instances from structural records * fix(dashboard): align inventory and act follow-up metrics * fix(web): align dashboard summary contract * fix(web): clarify dashboard act and report concepts * feat(inventory): mark field-created records as real instances * feat(inventory): map physical instance flag on asset entity * fix(inventory): keep field yacimientos structural * feat(inventory): classify future concrete instances at database level * fix(inventory): count only installation and subinstallation instances * feat(inventory): add authoritative F5 source snapshot * feat(inventory): preload authoritative territory model * feat(inventory): preload authoritative technical catalog * fix(findings): use only authoritative F5 family catalog * fix(inventory): preserve non-hierarchical operator snapshot compatibility * feat(inventory): add inventory-only asset filter * feat(inventory): add inventory-only tree filter * feat(inventory): add inventory browser query contract * feat(inventory): add area-owned inventory browser * feat(inventory): expose area-owned inventory browser * refactor(inventory): remove function catalog and add inventory browser * fix(inventory): make operator relation temporal and non-owning * feat(web): add inventory browser API client * feat(inventory): extend inventory browser filters * feat(inventory): add real inventory list endpoint logic * feat(inventory): expose real inventory list * feat(web): add real inventory list client * refactor(web): make inventory hierarchy area-owned * fix(web): show only real inventory instances * fix(web): style act follow-up tabs and F5 inventory context * fix(web): load F5 flow styles * fix(inventory): apply area-owned operational guard on F5 up * fix(inventory): treat company on asset as non-owning creation snapshot * fix(inventory): resolve field inventory by area hierarchy, not company ownership * fix(inventory): preserve custom catalog and apply authoritative universal findings * fix(inventory): harden authoritative catalog migration checks * fix(inventory): make authoritative territory preload safely reversible * feat(inventory): allow independent company master creation * fix(inventory): make guided creation area-owned and support companies * feat(web): expose independent company master in inventory setup * feat(web): create companies independently from physical inventory hierarchy * fix(inventory): merge by physical area and preserve sealed documents * test(inventory): lock F5 authoritative model and merge invariants * feat(inventory): add family administration DTOs * feat(inventory): administer installation and subinstallation classifications * feat(inventory): expose family classification administration * feat(web): add inventory classification administration API * fix(web): configure finding applicability by inventory classification * fix(web): redefine inventory configuration around hierarchy classifications and columns * chore(release): identify F5 inventory model * chore(release): bump API for F5 inventory model * test(release): expect F5 health metadata * chore(release): align WEB package with F5 inventory cut * chore(release): expose F5 WEB phase * test(dashboard): expect inspector activity and act follow-up metrics * test(dashboard): route F5 summary query mocks explicitly * ci: rehearse all migrations on clean PostGIS before merge * ci: prove F5 migrations revert and reapply cleanly * test(f5): align operational navigation contract * test(f5): align operator lifecycle with area-owned inventory * test(f5): make merge compatibility area-based * test(f5): distinguish literal and normalized yacimiento counts * test(f5): model normalized yacimiento collision explicitly * ci(f5): bootstrap historical admin prerequisite in clean migration rehearsal * ci(f5): bypass irreversible historical reset in clean rehearsal * fix(f5): make territory SQL parameter types explicit * fix(f5): guarantee canonical inventory hierarchy before territory preload * ci(f5): include canonical hierarchy migration in rollback gate * fix(f5): type relation backup markers explicitly * fix(f5): make catalog SQL text parameter types explicit
233 lines
9.6 KiB
YAML
233 lines
9.6 KiB
YAML
name: DH V2 CI
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [main]
|
|
push:
|
|
branches: [main]
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: dhv2-ci-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
api:
|
|
name: API · typecheck, tests, build
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: api-v3
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '24'
|
|
cache: npm
|
|
cache-dependency-path: api-v3/package-lock.json
|
|
- run: npm ci
|
|
- run: npm run typecheck
|
|
- run: npm test
|
|
- run: npm run build
|
|
|
|
web:
|
|
name: WEB · typecheck, build
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: web-v2
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '24'
|
|
cache: npm
|
|
cache-dependency-path: web-v2/package-lock.json
|
|
- run: npm ci
|
|
- run: npm run typecheck
|
|
- name: F3.1 WEB contract
|
|
run: bash ../scripts/check-f3-1-web-contract.sh
|
|
- run: npm run build
|
|
|
|
contract:
|
|
name: Docker / scripts contract
|
|
runs-on: ubuntu-latest
|
|
needs: [api, web]
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Validate shell scripts
|
|
run: |
|
|
while IFS= read -r -d '' script; do
|
|
bash -n "$script"
|
|
done < <(find scripts -type f -name '*.sh' -print0)
|
|
- name: Validate deploy preflight parity
|
|
run: |
|
|
grep -Fq -- '$STAGE/docker-compose.yml:/docker-compose.yml:ro' scripts/deploy-github.sh
|
|
grep -Fq -- '$STAGE/web-v2:/web-v2:ro' scripts/deploy-github.sh
|
|
grep -Fq -- '$STAGE/android-app:/android-app:ro' scripts/deploy-github.sh
|
|
- name: Validate Compose
|
|
run: docker compose --env-file .env.example config >/dev/null
|
|
- name: Rehearse migrations on clean PostGIS
|
|
run: |
|
|
set -Eeuo pipefail
|
|
cleanup() {
|
|
docker compose --env-file .env.example --profile tools down -v --remove-orphans >/dev/null 2>&1 || true
|
|
}
|
|
trap cleanup EXIT
|
|
cleanup
|
|
|
|
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.
|
|
bootstrap_log="$(mktemp)"
|
|
set +e
|
|
docker compose --env-file .env.example --profile tools run --build --rm migrate 2>&1 | tee "$bootstrap_log"
|
|
bootstrap_status=${PIPESTATUS[0]}
|
|
set -e
|
|
if [ "$bootstrap_status" -eq 0 ]; then
|
|
echo "ERROR: clean migration rehearsal unexpectedly passed the historical production reset." >&2
|
|
exit 1
|
|
fi
|
|
grep -Fq 'Production reset aborted: expected exactly one username admin, found 0' "$bootstrap_log" || {
|
|
echo "ERROR: migration rehearsal failed before the expected historical production-reset guard." >&2
|
|
exit 1
|
|
}
|
|
rm -f "$bootstrap_log"
|
|
|
|
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;
|
|
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 $$;
|
|
SQL
|
|
|
|
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 <<'SQL'
|
|
DO $$
|
|
DECLARE
|
|
f5_migrations integer;
|
|
real_inventory integer;
|
|
source_areas integer;
|
|
source_yacimientos integer;
|
|
source_installations integer;
|
|
source_subinstallations integer;
|
|
source_findings integer;
|
|
BEGIN
|
|
SELECT COUNT(*) INTO f5_migrations
|
|
FROM typeorm_migrations
|
|
WHERE name IN (
|
|
'F5InventoryPhysicalInstance1790087100000',
|
|
'F5CanonicalInventoryHierarchy1790087150000',
|
|
'F5AuthoritativeTerritory1790087200000',
|
|
'F5OperationalContextCompatibility1790087250000',
|
|
'F5AuthoritativeInventoryCatalog1790087300000'
|
|
);
|
|
IF f5_migrations <> 5 THEN
|
|
RAISE EXCEPTION 'Expected 5 F5 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;
|
|
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;
|
|
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%';
|
|
IF source_installations <> 14 OR source_subinstallations <> 109 THEN
|
|
RAISE EXCEPTION 'F5 family preload mismatch: installations %, subinstallations %', 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 'F5 finding preload mismatch: %', source_findings;
|
|
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'
|
|
- name: VPS-equivalent isolated API preflight
|
|
run: |
|
|
set -Eeuo pipefail
|
|
image="dhv2-api:ci-vps-preflight-${GITHUB_SHA::12}"
|
|
docker build --target builder -t "$image" api-v3
|
|
docker run --rm \
|
|
-v "$PWD/api-v3/test:/app/test:ro" \
|
|
-v "$PWD/api-v3/tsconfig.test.json:/app/tsconfig.test.json:ro" \
|
|
-v "$PWD/docker-compose.yml:/docker-compose.yml:ro" \
|
|
-v "$PWD/web-v2:/web-v2:ro" \
|
|
-v "$PWD/android-app:/android-app:ro" \
|
|
"$image" npm test
|
|
docker image rm "$image" >/dev/null 2>&1 || true
|
|
- name: Build production images
|
|
run: docker compose --env-file .env.example build api migrate web
|