From 009ed6dafaa6fda1ea427af01b64a6b9ba0a4f5a Mon Sep 17 00:00:00 2001 From: SimpleTest Date: Wed, 12 Aug 2026 16:32:11 +0300 Subject: [PATCH] Enforce migration compatibility registry --- .../migration_application_compatibility.tsv | 1 + scripts/check-migration-registry.sh | 76 +++++++++++++++++++ scripts/quality.sh | 1 + scripts/release-migration-policy-drill.sh | 14 ++++ 4 files changed, 92 insertions(+) create mode 100755 scripts/check-migration-registry.sh diff --git a/priv/repo/migration_application_compatibility.tsv b/priv/repo/migration_application_compatibility.tsv index 1bd6afa..d876848 100644 --- a/priv/repo/migration_application_compatibility.tsv +++ b/priv/repo/migration_application_compatibility.tsv @@ -9,3 +9,4 @@ 20260801123806 application_safe additive concurrent user directory search indexes 20260801141743 application_safe additive concurrent operations queue search indexes 20260801200302 application_safe additive generated public discovery coordinate columns +20260812120611 application_safe dropping the delivery-channel check allows inbox-only subscriptions that old code can still read safely diff --git a/scripts/check-migration-registry.sh b/scripts/check-migration-registry.sh new file mode 100755 index 0000000..c968ee7 --- /dev/null +++ b/scripts/check-migration-registry.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT=${1:-$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)} +registry="$ROOT/priv/repo/migration_application_compatibility.tsv" +migrations_dir="$ROOT/priv/repo/migrations" + +[[ -f "$registry" ]] || { + echo "Migration compatibility registry is missing: $registry" >&2 + exit 2 +} + +[[ -d "$migrations_dir" ]] || { + echo "Migration directory is missing: $migrations_dir" >&2 + exit 2 +} + +mapfile -t entries < <( + awk -F '\t' ' + $0 !~ /^#/ && NF > 0 { + if (NF != 3 || $1 !~ /^[0-9]{14}$/ || + ($2 != "application_safe" && $2 != "forward_only") || + $3 == "" || $3 == $2) { + print "INVALID\t" NR "\t" $0 + } else { + print $1 "\t" $2 "\t" $3 + } + } + ' "$registry" +) + +if [[ ${#entries[@]} -eq 0 ]]; then + echo "Migration compatibility registry has no reviewed entries." >&2 + exit 2 +fi + +for entry in "${entries[@]}"; do + if [[ "$entry" == INVALID$'\t'* ]]; then + echo "Invalid migration registry entry: $entry" >&2 + exit 2 + fi +done + +mapfile -t versions < <(printf '%s\n' "${entries[@]}" | cut -f1) +if [[ $(printf '%s\n' "${versions[@]}" | sort -u | wc -l) -ne ${#versions[@]} ]]; then + echo "Migration compatibility registry contains a duplicate version." >&2 + exit 2 +fi + +first_reviewed_version=$(printf '%s\n' "${versions[@]}" | sort | head -n 1) + +mapfile -t reviewed_migrations < <( + find "$migrations_dir" -maxdepth 1 -type f -name '[0-9]*.exs' -printf '%f\n' | + awk -F_ -v first="$first_reviewed_version" '$1 >= first {print $1}' | + sort +) + +for version in "${versions[@]}"; do + mapfile -t files < <( + find "$migrations_dir" -maxdepth 1 -type f -name "${version}_*.exs" -printf '%f\n' + ) + if [[ ${#files[@]} -ne 1 ]]; then + echo "Registry version $version must identify exactly one migration file." >&2 + exit 2 + fi +done + +for version in "${reviewed_migrations[@]}"; do + matches=$(printf '%s\n' "${versions[@]}" | grep -Fxc "$version" || true) + if [[ "$matches" -ne 1 ]]; then + echo "Migration $version must have exactly one reviewed registry entry." >&2 + exit 2 + fi +done + +echo "Migration compatibility registry is complete and internally consistent." diff --git a/scripts/quality.sh b/scripts/quality.sh index f71a4cf..73e43ee 100755 --- a/scripts/quality.sh +++ b/scripts/quality.sh @@ -134,6 +134,7 @@ echo "Checking isolated production application rollback plan/apply" ./scripts/production-rollback-drill.sh echo "Checking release migration compatibility policy" +./scripts/check-migration-registry.sh ./scripts/release-migration-policy-drill.sh echo "Checking isolated production release orchestration" diff --git a/scripts/release-migration-policy-drill.sh b/scripts/release-migration-policy-drill.sh index f7db65f..1877d3a 100755 --- a/scripts/release-migration-policy-drill.sh +++ b/scripts/release-migration-policy-drill.sh @@ -22,6 +22,11 @@ git -C "$fixture" add . git -C "$fixture" commit --quiet -m baseline baseline=$(git -C "$fixture" rev-parse HEAD) +if "$ROOT/scripts/check-migration-registry.sh" "$fixture" >/dev/null 2>&1; then + echo "Migration registry checker accepted an empty registry." >&2 + exit 1 +fi + printf '%s\n' 'defmodule SafeMigration do end' \ >"$fixture/priv/repo/migrations/20260101000000_safe_migration.exs" printf '%s\t%s\t%s\n' \ @@ -31,6 +36,8 @@ git -C "$fixture" add . git -C "$fixture" commit --quiet -m safe safe=$(git -C "$fixture" rev-parse HEAD) +"$ROOT/scripts/check-migration-registry.sh" "$fixture" >/dev/null + safe_output=$( "$ROOT/scripts/release-migration-policy.sh" "$baseline" "$safe" "$fixture" ) @@ -47,6 +54,8 @@ git -C "$fixture" add . git -C "$fixture" commit --quiet -m forward forward=$(git -C "$fixture" rev-parse HEAD) +"$ROOT/scripts/check-migration-registry.sh" "$fixture" >/dev/null + forward_output=$( "$ROOT/scripts/release-migration-policy.sh" "$baseline" "$forward" "$fixture" ) @@ -74,6 +83,11 @@ git -C "$fixture" add . git -C "$fixture" commit --quiet -m unknown unknown=$(git -C "$fixture" rev-parse HEAD) +if "$ROOT/scripts/check-migration-registry.sh" "$fixture" >/dev/null 2>&1; then + echo "Migration registry checker accepted an unreviewed migration." >&2 + exit 1 +fi + if "$ROOT/scripts/release-migration-policy.sh" \ "$safe" "$unknown" "$fixture" >/dev/null 2>&1; then echo "Migration policy accepted an unreviewed migration." >&2