name: Apply F1 audit-chain advisory-lock migration via postgres superuser # Incident response: redeploy.yml fails on F1 migration because crm_migrator role # lacks privilege to CREATE OR REPLACE FUNCTION в schema public. # This workflow applies F1 migration SQL directly via sudo -u postgres psql, # then INSERTs the migration row so subsequent `php artisan migrate` skips it. # # Ref: docs/superpowers/plans/2026-05-29-audit-chain-race-fix.md Task 2 # Migration file: app/database/migrations/2026_05_30_000001_add_advisory_lock_to_audit_chain_hash.php on: workflow_dispatch: inputs: confirm_apply: description: 'Подтверждаю применение F1 миграции на проде' required: true default: 'false' type: boolean jobs: apply: runs-on: ubuntu-latest timeout-minutes: 5 env: LIDERRA_HOST: 111.88.246.137 LIDERRA_USER: ubuntu CONFIRM: ${{ github.event.inputs.confirm_apply }} steps: - name: Guard run: | if [[ "$CONFIRM" != "true" ]]; then echo "::error::confirm_apply=true required" exit 1 fi - name: Setup SSH key run: | mkdir -p ~/.ssh echo "${{ secrets.LIDERRA_SSH_KEY }}" > ~/.ssh/liderra_deploy chmod 600 ~/.ssh/liderra_deploy ssh-keyscan -H ${{ env.LIDERRA_HOST }} >> ~/.ssh/known_hosts 2>/dev/null - name: Apply F1 SQL + register migration run: | ssh -i ~/.ssh/liderra_deploy ${{ env.LIDERRA_USER }}@${{ env.LIDERRA_HOST }} \ "bash -s" <<'REMOTE' | tee /tmp/f1-apply.log set +e echo "=== 1. BEFORE: current audit_chain_hash function source ===" sudo -u postgres psql -d liderra -c "\df+ public.audit_chain_hash" 2>&1 | head -20 echo echo "=== 2. Apply F1 advisory-lock migration via sudo -u postgres ===" sudo -u postgres psql -d liderra <<'SQL' CREATE OR REPLACE FUNCTION public.audit_chain_hash() RETURNS trigger AS $$ DECLARE prev_hash BYTEA; lock_key BIGINT; BEGIN lock_key := ('x' || lpad(to_hex(TG_RELID::int), 16, '0'))::bit(64)::bigint; PERFORM pg_advisory_xact_lock(lock_key); EXECUTE format( 'SELECT log_hash FROM %I ORDER BY id DESC LIMIT 1', TG_TABLE_NAME ) INTO prev_hash; NEW.log_hash := digest( COALESCE(prev_hash, ''::bytea) || NEW::text::bytea, 'sha256' ); RETURN NEW; END; $$ LANGUAGE plpgsql; SQL APPLY_RC=$? echo "Apply RC: $APPLY_RC" echo echo "=== 3. Verify function now contains pg_advisory_xact_lock ===" sudo -u postgres psql -d liderra -c "SELECT pg_get_functiondef('public.audit_chain_hash'::regproc) LIKE '%pg_advisory_xact_lock%' AS has_lock" echo echo "=== 4. Register migration row (skip if already exists) ===" sudo -u postgres psql -d liderra <<'SQL' INSERT INTO migrations (migration, batch) SELECT '2026_05_30_000001_add_advisory_lock_to_audit_chain_hash', COALESCE(MAX(batch),0)+1 FROM migrations WHERE NOT EXISTS ( SELECT 1 FROM migrations WHERE migration = '2026_05_30_000001_add_advisory_lock_to_audit_chain_hash' ); SELECT migration, batch FROM migrations WHERE migration LIKE '%advisory_lock%'; SQL echo echo "=== DONE ===" REMOTE - name: Print summary if: always() run: | { echo "## F1 migration apply" echo echo '```' cat /tmp/f1-apply.log 2>/dev/null || echo "(no log)" echo '```' } >> "$GITHUB_STEP_SUMMARY" - name: Cleanup SSH key if: always() run: rm -f ~/.ssh/liderra_deploy