All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m32s
CD Pipeline / build-and-deploy (push) Successful in 7m58s
CD Pipeline / post-deploy-checks (push) Successful in 2m25s
216 lines
5.8 KiB
SQL
216 lines
5.8 KiB
SQL
-- Repair duplicate canonical asset keys without deleting historical rows.
|
|
-- Safety: bounded quarantine, transactional index rebuild, independent verifier.
|
|
|
|
SET lock_timeout = '10s';
|
|
SET statement_timeout = '90s';
|
|
SET enable_indexscan = 'off';
|
|
SET enable_indexonlyscan = 'off';
|
|
SET enable_bitmapscan = 'off';
|
|
|
|
CREATE TEMP TABLE asset_inventory_integrity_repair_map
|
|
ON COMMIT DROP
|
|
AS
|
|
WITH ranked_assets AS (
|
|
SELECT
|
|
asset_id,
|
|
asset_key AS original_asset_key,
|
|
FIRST_VALUE(asset_id) OVER (
|
|
PARTITION BY asset_key
|
|
ORDER BY first_seen_at ASC, asset_id ASC
|
|
) AS canonical_asset_id,
|
|
ROW_NUMBER() OVER (
|
|
PARTITION BY asset_key
|
|
ORDER BY first_seen_at ASC, asset_id ASC
|
|
) AS duplicate_rank
|
|
FROM asset_inventory
|
|
)
|
|
SELECT
|
|
asset_id,
|
|
original_asset_key,
|
|
canonical_asset_id
|
|
FROM ranked_assets
|
|
WHERE duplicate_rank > 1;
|
|
|
|
DO $$
|
|
DECLARE
|
|
repair_row_count BIGINT;
|
|
BEGIN
|
|
SELECT COUNT(*) INTO repair_row_count
|
|
FROM asset_inventory_integrity_repair_map;
|
|
|
|
IF repair_row_count > 100 THEN
|
|
RAISE EXCEPTION
|
|
'asset_inventory_integrity_repair_bound_exceeded: % rows',
|
|
repair_row_count;
|
|
END IF;
|
|
END
|
|
$$;
|
|
|
|
UPDATE asset_inventory AS duplicate_asset
|
|
SET
|
|
asset_key = duplicate_asset.asset_key
|
|
|| '/__integrity_quarantine__/'
|
|
|| duplicate_asset.asset_id::TEXT,
|
|
lifecycle_state = 'deprecated',
|
|
decommissioned_at = COALESCE(duplicate_asset.decommissioned_at, NOW()),
|
|
metadata = COALESCE(duplicate_asset.metadata, '{}'::JSONB)
|
|
|| JSONB_BUILD_OBJECT(
|
|
'integrity_state', 'quarantined_duplicate',
|
|
'integrity_canonical_asset_id', repair.canonical_asset_id,
|
|
'integrity_repair', 'asset_inventory_canonical_integrity_2026-07-14'
|
|
),
|
|
tags = CASE
|
|
WHEN 'integrity:quarantined_duplicate' = ANY(duplicate_asset.tags)
|
|
THEN duplicate_asset.tags
|
|
ELSE ARRAY_APPEND(
|
|
duplicate_asset.tags,
|
|
'integrity:quarantined_duplicate'
|
|
)
|
|
END,
|
|
updated_at = NOW()
|
|
FROM asset_inventory_integrity_repair_map AS repair
|
|
WHERE duplicate_asset.asset_id = repair.asset_id;
|
|
|
|
-- Rebuild the unique index after every formerly unindexed duplicate has a
|
|
-- distinct quarantine key. The outer CD transaction rolls back on any error.
|
|
REINDEX INDEX asset_inventory_asset_key_key;
|
|
|
|
DO $$
|
|
DECLARE
|
|
duplicate_group_count BIGINT;
|
|
repaired_row_count BIGINT;
|
|
expected_repair_count BIGINT;
|
|
index_healthy BOOLEAN;
|
|
BEGIN
|
|
SELECT COUNT(*) INTO duplicate_group_count
|
|
FROM (
|
|
SELECT asset_key
|
|
FROM asset_inventory
|
|
GROUP BY asset_key
|
|
HAVING COUNT(*) > 1
|
|
) AS duplicate_groups;
|
|
|
|
SELECT COUNT(*) INTO expected_repair_count
|
|
FROM asset_inventory_integrity_repair_map;
|
|
|
|
SELECT COUNT(*) INTO repaired_row_count
|
|
FROM asset_inventory AS repaired
|
|
JOIN asset_inventory_integrity_repair_map AS repair
|
|
ON repair.asset_id = repaired.asset_id
|
|
WHERE repaired.lifecycle_state = 'deprecated'
|
|
AND repaired.asset_key = repair.original_asset_key
|
|
|| '/__integrity_quarantine__/'
|
|
|| repaired.asset_id::TEXT
|
|
AND repaired.metadata ->> 'integrity_state' = 'quarantined_duplicate';
|
|
|
|
SELECT
|
|
index_row.indisvalid
|
|
AND index_row.indisready
|
|
AND index_row.indislive
|
|
AND index_row.indisunique
|
|
INTO index_healthy
|
|
FROM pg_index AS index_row
|
|
WHERE index_row.indexrelid = 'asset_inventory_asset_key_key'::REGCLASS;
|
|
|
|
IF duplicate_group_count <> 0 THEN
|
|
RAISE EXCEPTION
|
|
'asset_inventory_duplicate_groups_remain: %',
|
|
duplicate_group_count;
|
|
END IF;
|
|
|
|
IF repaired_row_count <> expected_repair_count THEN
|
|
RAISE EXCEPTION
|
|
'asset_inventory_quarantine_verifier_failed: expected %, got %',
|
|
expected_repair_count,
|
|
repaired_row_count;
|
|
END IF;
|
|
|
|
IF index_healthy IS DISTINCT FROM TRUE THEN
|
|
RAISE EXCEPTION 'asset_inventory_unique_index_not_healthy';
|
|
END IF;
|
|
END
|
|
$$;
|
|
|
|
-- Exercise the exact conflict-update path that failed before the repair while
|
|
-- preserving every application value.
|
|
WITH probe AS (
|
|
SELECT
|
|
asset_key,
|
|
asset_type,
|
|
host,
|
|
namespace,
|
|
name,
|
|
metadata,
|
|
tags,
|
|
environment,
|
|
lifecycle_state,
|
|
first_seen_at,
|
|
last_seen_at
|
|
FROM asset_inventory
|
|
ORDER BY asset_id
|
|
LIMIT 1
|
|
)
|
|
INSERT INTO asset_inventory (
|
|
asset_key,
|
|
asset_type,
|
|
host,
|
|
namespace,
|
|
name,
|
|
metadata,
|
|
tags,
|
|
environment,
|
|
lifecycle_state,
|
|
first_seen_at,
|
|
last_seen_at
|
|
)
|
|
SELECT
|
|
asset_key,
|
|
asset_type,
|
|
host,
|
|
namespace,
|
|
name,
|
|
metadata,
|
|
tags,
|
|
environment,
|
|
lifecycle_state,
|
|
first_seen_at,
|
|
last_seen_at
|
|
FROM probe
|
|
ON CONFLICT (asset_key) DO UPDATE
|
|
SET
|
|
last_seen_at = asset_inventory.last_seen_at,
|
|
updated_at = asset_inventory.updated_at;
|
|
|
|
INSERT INTO automation_operation_log (
|
|
operation_type,
|
|
actor,
|
|
status,
|
|
input,
|
|
output,
|
|
tags
|
|
)
|
|
SELECT
|
|
'asset_discovered',
|
|
'gitea_cd_asset_integrity_reconciler',
|
|
'success',
|
|
'{"work_item_id":"AIA-P0-006","migration":"asset_inventory_canonical_integrity_2026-07-14"}'::JSONB,
|
|
JSONB_BUILD_OBJECT(
|
|
'duplicate_group_count_before', COUNT(DISTINCT original_asset_key),
|
|
'rows_quarantined', COUNT(*),
|
|
'duplicate_group_count_after', 0,
|
|
'index_rebuilt', TRUE,
|
|
'canonical_upsert_verified', TRUE,
|
|
'data_rows_deleted', 0
|
|
),
|
|
ARRAY[
|
|
'asset_control_plane',
|
|
'integrity_repair',
|
|
'controlled_apply',
|
|
'post_verified',
|
|
'no_delete'
|
|
]
|
|
FROM asset_inventory_integrity_repair_map;
|
|
|
|
COMMENT ON INDEX asset_inventory_asset_key_key IS
|
|
'Canonical asset identity unique index; transactionally rebuilt and verified by Gitea CD.';
|