Skip to content

feat(archives): RiC-CM full roadmap (Phases 1-6) — RiC-O Linked Data, agents/activities/places, admin UI, OAI-PMH ric-o (v0.7.12, #122) #68

feat(archives): RiC-CM full roadmap (Phases 1-6) — RiC-O Linked Data, agents/activities/places, admin UI, OAI-PMH ric-o (v0.7.12, #122)

feat(archives): RiC-CM full roadmap (Phases 1-6) — RiC-O Linked Data, agents/activities/places, admin UI, OAI-PMH ric-o (v0.7.12, #122) #68

name: Upgrade Smoke Test
# Simulates a real-world upgrade: installs the latest stable release, then
# applies the current branch's migrations on top. Catches migration regressions
# that would break existing installations.
on:
push:
branches: [main]
paths:
- 'installer/database/**'
- 'version.json'
- '.github/workflows/ci-upgrade-smoke.yml'
pull_request:
branches: [main]
paths:
- 'installer/database/**'
- 'version.json'
- '.github/workflows/ci-upgrade-smoke.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
upgrade-smoke:
name: Upgrade from latest release → verify schema
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: pinakes_upgrade
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=5
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
coverage: none
github-token: ''
- name: Wait for MySQL
run: until mysqladmin ping -h"127.0.0.1" --silent; do sleep 1; done
# ── Step 1: Resolve latest stable release ─────────────────────────────
- name: Resolve latest release
id: release
env:
GH_REPO: ${{ github.repository }}
run: |
HTTP_STATUS=$(curl -s -o /tmp/release.json -w "%{http_code}" \
"https://api.github.com/repos/${GH_REPO}/releases/latest")
if [ "$HTTP_STATUS" = "404" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "No releases found (HTTP 404) — skipping upgrade path (fresh install test only)"
exit 0
elif [ "$HTTP_STATUS" != "200" ]; then
echo "GitHub API returned HTTP $HTTP_STATUS — aborting"
exit 1
fi
TAG=$(python3 -c "import json; d=json.load(open('/tmp/release.json')); print(d.get('tag_name',''))")
ASSET=$(python3 -c "import json; d=json.load(open('/tmp/release.json')); assets=[a['browser_download_url'] for a in d.get('assets',[]) if a['name'].endswith('.zip')]; print(assets[0] if assets else '')")
if [ -z "$TAG" ] || [ -z "$ASSET" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "No ZIP asset found in latest release — skipping upgrade path"
exit 0
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "asset=$ASSET" >> "$GITHUB_OUTPUT"
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "Latest release: $TAG ($ASSET)"
# ── Step 2: Install released schema ───────────────────────────────────
- name: Install released schema (baseline)
if: steps.release.outputs.skip == 'false'
env:
ASSET_URL: ${{ steps.release.outputs.asset }}
RELEASE_TAG: ${{ steps.release.outputs.tag }}
run: |
curl -sfL -o /tmp/release.zip "$ASSET_URL"
mkdir -p /tmp/pinakes-release
unzip -q /tmp/release.zip -d /tmp/pinakes-release
SCHEMA=$(find /tmp/pinakes-release -name "schema.sql" -path "*/database/*" | head -1)
if [ -z "$SCHEMA" ]; then
echo "schema.sql not found in release ZIP — aborting"
exit 1
fi
echo "Using schema: $SCHEMA (from $RELEASE_TAG)"
mysql -h 127.0.0.1 -u root -proot pinakes_upgrade < "$SCHEMA"
echo "✓ Base schema installed from $RELEASE_TAG"
# ── Step 3: Apply released migrations (idempotent baseline) ───────────
# Released migrations run on top of the released schema.sql which is
# already fully migrated up to that release. Every error here is by
# definition idempotent: duplicate columns/keys, FKs that already exist,
# RENAME/CHANGE on columns that were already renamed in schema.sql, etc.
# The extended idempotent filter covers cases that older migration files
# (pre-guard era) produce when re-run on an already-migrated schema.
- name: Apply released migrations
if: steps.release.outputs.skip == 'false'
run: |
MIGRATIONS=$(find /tmp/pinakes-release -type d -name "migrations" | head -1)
if [ -z "$MIGRATIONS" ]; then
echo "No migrations dir in release ZIP — skipping"
exit 0
fi
FAILED=0
for sql in $(ls "$MIGRATIONS"/migrate_*.sql 2>/dev/null | sort -V); do
echo "→ $(basename $sql)"
OUTPUT=$(mysql --force -h 127.0.0.1 -u root -proot pinakes_upgrade < "$sql" 2>&1) || true
while IFS= read -r line; do
if echo "$line" | grep -qiE '^ERROR'; then
if echo "$line" | grep -qiE \
'duplicate (column name|key|index)|table .* already exists|check constraint .* already exists|can.t drop|duplicate foreign key constraint name|unknown column'; then
echo " ↳ idempotent: $line"
else
echo " ✗ $line"
FAILED=1
fi
fi
done <<< "$OUTPUT"
done
[ "$FAILED" -eq 0 ] || exit 1
echo "✓ Released migrations applied"
# ── Step 4: Fresh-install fallback (no prior release) ─────────────────
- name: Install base schema (fresh-install fallback)
if: steps.release.outputs.skip == 'true'
run: |
mysql -h 127.0.0.1 -u root -proot pinakes_upgrade < installer/database/schema.sql
echo "✓ Base schema installed from current branch (no prior release)"
# ── Step 5: Apply current branch migrations (upgrade simulation) ───────
- name: Apply current branch migrations
run: |
FAILED=0
for sql in $(ls installer/database/migrations/migrate_*.sql | sort -V); do
echo "→ $(basename $sql)"
OUTPUT=$(mysql --force -h 127.0.0.1 -u root -proot pinakes_upgrade < "$sql" 2>&1) || true
while IFS= read -r line; do
if echo "$line" | grep -qiE '^ERROR'; then
if echo "$line" | grep -qiE 'duplicate (column name|key|index)|table .* already exists|check constraint .* already exists|can.t drop|duplicate foreign key constraint name'; then
echo " ↳ idempotent: $line"
else
echo " ✗ $line"
FAILED=1
fi
fi
done <<< "$OUTPUT"
done
[ "$FAILED" -eq 0 ] || exit 1
echo "✓ Current branch migrations applied"
# ── Step 6: Verify schema after upgrade ───────────────────────────────
- name: Verify core tables present
run: |
CORE_TABLES="admin_notifications api_keys autori author_authority_alternates cms_pages collane consent_log contact_messages copie digital_assets donazioni editori email_templates events feedback gdpr_requests generi home_content import_logs languages libri libri_autori libri_collane libri_donati libri_tag log_modifiche mag_project_config mensole migrations ncip_partners ncip_transactions oai_deleted_records oai_resumption_tokens plugin_data plugin_hooks plugin_logs plugin_settings plugins posizioni preferenze_notifica_utenti prenotazioni prestiti recensioni scaffali sedi staff system_settings tag themes update_logs user_sessions utenti volumi wishlist z39_access_logs z39_rate_limits"
FAILED=0
MISSING=""
for t in $CORE_TABLES; do
EXISTS=$(mysql -h 127.0.0.1 -u root -proot pinakes_upgrade -sN -e \
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='pinakes_upgrade' AND table_name='$t'")
if [ "$EXISTS" -eq 0 ]; then
MISSING="$MISSING $t"
FAILED=1
fi
done
if [ "$FAILED" -eq 0 ]; then
echo "✓ All $(echo $CORE_TABLES | wc -w | tr -d ' ') core tables present"
else
echo "✗ Missing tables:$MISSING"
exit 1
fi
- name: Verify ENUM values
run: |
FAILED=0
V=$(mysql -h 127.0.0.1 -u root -proot pinakes_upgrade -sN -e \
"SELECT COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS \
WHERE TABLE_SCHEMA='pinakes_upgrade' \
AND TABLE_NAME='oai_deleted_records' \
AND COLUMN_NAME='entity_type'" 2>/dev/null || echo "")
if [ -n "$V" ]; then
echo "oai_deleted_records.entity_type: $V"
echo "$V" | grep -q "archival_unit" && echo " ✓ 'archival_unit' present" || { echo " ✗ 'archival_unit' missing"; FAILED=1; }
echo "$V" | grep -q "'archive_unit'" && { echo " ✗ old typo 'archive_unit' still present"; FAILED=1; } || true
fi
V=$(mysql -h 127.0.0.1 -u root -proot pinakes_upgrade -sN -e \
"SELECT COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS \
WHERE TABLE_SCHEMA='pinakes_upgrade' \
AND TABLE_NAME='prestiti' \
AND COLUMN_NAME='origine'" 2>/dev/null || echo "")
if [ -n "$V" ]; then
echo "prestiti.origine: $V"
echo "$V" | grep -q "'ncip'" && echo " ✓ 'ncip' present" || { echo " ✗ 'ncip' missing from prestiti.origine"; FAILED=1; }
fi
[ "$FAILED" -eq 0 ] || exit 1
- name: Verify key columns from recent migrations
run: |
FAILED=0
check_col() {
local TABLE="$1" COL="$2"
EXISTS=$(mysql -h 127.0.0.1 -u root -proot pinakes_upgrade -sN -e \
"SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='pinakes_upgrade' AND table_name='$TABLE' AND column_name='$COL'")
if [ "$EXISTS" -eq 0 ]; then
echo " ✗ Missing: $TABLE.$COL"
FAILED=1
else
echo " ✓ $TABLE.$COL"
fi
}
# v0.7.0 — VIAF/ISNI
check_col autori viaf_id
check_col autori isni_id
# v0.7.3 — NCIP
check_col prestiti origine
# v0.7.4 — NCIP partner attrs
check_col ncip_partners isil
check_col ncip_partners notes
# v0.7.4 — digital_assets
check_col digital_assets libro_id
check_col digital_assets url
check_col digital_assets md5_hash
check_col digital_assets filesize
check_col digital_assets filetype
[ "$FAILED" -eq 0 ] || exit 1
- name: Verify plugin registrations
run: |
EXPECTED="api-book-scraper archives bibframe-linked-data deezer dewey-editor digital-library discogs goodlib musicbrainz ncip-server oai-pmh-server open-library openurl-resolver resource-sync viaf-authority z39-server"
FAILED=0
for plugin in $EXPECTED; do
EXISTS=$(mysql -h 127.0.0.1 -u root -proot pinakes_upgrade -sN -e \
"SELECT COUNT(*) FROM plugins WHERE name='$plugin'" 2>/dev/null || echo "0")
if [ "$EXISTS" -eq 0 ]; then
echo " ✗ Plugin not registered: $plugin"
FAILED=1
else
echo " ✓ $plugin"
fi
done
[ "$FAILED" -eq 0 ] || exit 1