1099 lines
53 KiB
Bash
Executable File
1099 lines
53 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$ROOT_DIR"
|
|
|
|
fail() {
|
|
echo "release-preflight: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
require_file() {
|
|
[[ -f "$1" ]] || fail "missing artifact: $1; build it before release"
|
|
}
|
|
|
|
sha256() {
|
|
if command -v shasum >/dev/null 2>&1; then
|
|
shasum -a 256 "$1" | awk '{print $1}'
|
|
else
|
|
sha256sum "$1" | awk '{print $1}'
|
|
fi
|
|
}
|
|
|
|
sha256_stream() {
|
|
if command -v shasum >/dev/null 2>&1; then
|
|
shasum -a 256 | awk '{print $1}'
|
|
else
|
|
sha256sum | awk '{print $1}'
|
|
fi
|
|
}
|
|
|
|
normalized_jar_content_sha256() {
|
|
local jar_path="$1"
|
|
local manifest_path
|
|
manifest_path="$(mktemp)"
|
|
while IFS= read -r entry; do
|
|
case "$entry" in
|
|
*/|META-INF/*.SF|META-INF/*.RSA|META-INF/*.DSA) continue ;;
|
|
esac
|
|
printf '%s %s\n' "$(unzip -p "$jar_path" "$entry" | sha256_stream)" "$entry" >> "$manifest_path"
|
|
done < <(unzip -Z1 "$jar_path" | LC_ALL=C sort)
|
|
sha256 "$manifest_path"
|
|
rm -f "$manifest_path"
|
|
}
|
|
|
|
verify_remote_match="${RELEASE_VERIFY_REMOTE_MATCH:-false}"
|
|
verify_remote_backend="${RELEASE_VERIFY_REMOTE_BACKEND:-false}"
|
|
verify_remote_schema="${RELEASE_VERIFY_REMOTE_SCHEMA:-false}"
|
|
verify_aug1_readiness="${RELEASE_VERIFY_AUG1_READINESS:-false}"
|
|
remote_verification_requested="false"
|
|
if [[ "$verify_remote_match" == "true" || "$verify_remote_backend" == "true" \
|
|
|| "$verify_remote_schema" == "true" || "$verify_aug1_readiness" == "true" ]]; then
|
|
remote_verification_requested="true"
|
|
fi
|
|
if [[ "$remote_verification_requested" == "true" && -z "${RELEASE_REMOTE_URL:-}" ]]; then
|
|
fail "remote verification flags require RELEASE_REMOTE_URL"
|
|
fi
|
|
|
|
frontend_index="frontend/dist/index.html"
|
|
mobile_index="mobile-uni/dist/build/h5/index.html"
|
|
backend_jar="${RELEASE_LOCAL_BACKEND_PATH:-backend/ruoyi-admin/target/ruoyi-admin.jar}"
|
|
artifact_commit="${RELEASE_ARTIFACT_COMMIT:-HEAD}"
|
|
artifact_commit_sha="$(git rev-parse --verify "${artifact_commit}^{commit}" 2>/dev/null)" \
|
|
|| fail "RELEASE_ARTIFACT_COMMIT is not a valid commit: $artifact_commit"
|
|
git merge-base --is-ancestor "$artifact_commit_sha" HEAD \
|
|
|| fail "RELEASE_ARTIFACT_COMMIT must be an ancestor of HEAD: $artifact_commit_sha"
|
|
require_static_artifacts="true"
|
|
require_backend_artifact="true"
|
|
if [[ "$remote_verification_requested" == "true" ]]; then
|
|
require_static_artifacts="$verify_remote_match"
|
|
require_backend_artifact="$verify_remote_backend"
|
|
fi
|
|
|
|
if [[ "$require_static_artifacts" == "true" ]]; then
|
|
require_file "$frontend_index"
|
|
require_file "$mobile_index"
|
|
frontend_asset="$(sed -nE 's/.*src="([^"]+\.js)".*/\1/p' "$frontend_index" | head -1)"
|
|
mobile_asset="$(sed -nE 's/.*src="([^"]+\.js)".*/\1/p' "$mobile_index" | head -1)"
|
|
[[ -n "$frontend_asset" ]] || fail "frontend index does not reference a JavaScript entry"
|
|
[[ -n "$mobile_asset" ]] || fail "mobile H5 index does not reference a JavaScript entry"
|
|
|
|
frontend_asset_path="frontend/dist/${frontend_asset#/}"
|
|
mobile_asset_rel="${mobile_asset#/}"
|
|
mobile_asset_rel="${mobile_asset_rel#h5/}"
|
|
mobile_asset_path="mobile-uni/dist/build/h5/$mobile_asset_rel"
|
|
require_file "$frontend_asset_path"
|
|
require_file "$mobile_asset_path"
|
|
grep -q '/h5/' "$mobile_index" || fail "mobile H5 index does not contain the /h5/ base path"
|
|
fi
|
|
|
|
if [[ "$require_backend_artifact" == "true" ]]; then
|
|
require_file "$backend_jar"
|
|
backend_module_jar_name="$(unzip -Z1 "$backend_jar" | sed -nE 's#BOOT-INF/lib/(ruoyi-aihr-[^/]+\.jar)#\1#p' | head -1)"
|
|
[[ -n "$backend_module_jar_name" ]] || fail "backend jar does not contain the ruoyi-aihr module"
|
|
fi
|
|
|
|
if [[ "${AIHR_PRACTICE_RUNTIME_SCHEMA_BOOTSTRAP:-false}" == "true" ]]; then
|
|
fail "AIHR_PRACTICE_RUNTIME_SCHEMA_BOOTSTRAP must stay false for a release; apply the formal SQL migration instead"
|
|
fi
|
|
|
|
head_epoch="$(git show -s --format=%ct "$artifact_commit_sha")"
|
|
|
|
file_epoch() {
|
|
if stat -f %m "$1" >/dev/null 2>&1; then
|
|
stat -f %m "$1"
|
|
else
|
|
stat -c %Y "$1"
|
|
fi
|
|
}
|
|
|
|
require_fresh_artifact() {
|
|
local artifact="$1"
|
|
local artifact_epoch
|
|
artifact_epoch="$(file_epoch "$artifact")" || fail "cannot read artifact timestamp: $artifact"
|
|
[[ "$artifact_epoch" -ge "$head_epoch" ]] || fail "artifact is older than HEAD; rebuild before release: $artifact"
|
|
}
|
|
|
|
if [[ "$require_static_artifacts" == "true" ]]; then
|
|
require_fresh_artifact "$frontend_index"
|
|
require_fresh_artifact "$frontend_asset_path"
|
|
require_fresh_artifact "$mobile_index"
|
|
require_fresh_artifact "$mobile_asset_path"
|
|
fi
|
|
if [[ "$require_backend_artifact" == "true" ]]; then
|
|
require_fresh_artifact "$backend_jar"
|
|
fi
|
|
|
|
require_remote_business_success() {
|
|
local label="$1"
|
|
local url="$2"
|
|
local body
|
|
body="$(curl -fsS --max-time 15 "$url")" || fail "$label HTTP check failed: $url"
|
|
if ! printf '%s' "$body" | LC_ALL=C grep -Eq '^[[:space:]]*\{[[:space:]]*"code"[[:space:]]*:[[:space:]]*200([[:space:]]*[,}])'; then
|
|
fail "$label business check failed: $url"
|
|
fi
|
|
echo "$label=200 $url"
|
|
}
|
|
|
|
require_remote_practice_schema_guard() {
|
|
local remote_ssh="${RELEASE_REMOTE_SSH:-YCWY}"
|
|
local remote_service="${RELEASE_REMOTE_SERVICE:-wygj-aihr.service}"
|
|
[[ "$remote_service" =~ ^[A-Za-z0-9_.@-]+\.service$ ]] \
|
|
|| fail "remote systemd service name is invalid: $remote_service"
|
|
|
|
local state
|
|
state="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_service" <<'REMOTE'
|
|
set -euo pipefail
|
|
service="$1"
|
|
matches_enabled_guard() {
|
|
grep -Eiq '(AIHR_PRACTICE_RUNTIME_SCHEMA_BOOTSTRAP|aihr\.practice\.runtime-schema-bootstrap)[[:space:]]*=[[:space:]]*"?true"?'
|
|
}
|
|
|
|
runtime_environment="$(systemctl show "$service" --property=Environment --value --no-pager)"
|
|
unit_source="$(systemctl cat "$service")"
|
|
if printf '%s\n' "$runtime_environment" | matches_enabled_guard \
|
|
|| printf '%s\n' "$unit_source" | matches_enabled_guard; then
|
|
printf 'enabled\n'
|
|
exit 0
|
|
fi
|
|
|
|
while IFS= read -r environment_file; do
|
|
[[ -z "$environment_file" ]] && continue
|
|
if [[ ! -r "$environment_file" ]]; then
|
|
printf 'unreadable:%s\n' "$environment_file"
|
|
exit 0
|
|
fi
|
|
if matches_enabled_guard < "$environment_file"; then
|
|
printf 'enabled\n'
|
|
exit 0
|
|
fi
|
|
done < <(printf '%s\n' "$unit_source" | sed -nE 's/^[[:space:]]*EnvironmentFile=-?([^[:space:]#]+).*/\1/p')
|
|
|
|
printf 'disabled-or-default\n'
|
|
REMOTE
|
|
)" || fail "remote runtime schema guard check failed: $remote_ssh:$remote_service"
|
|
|
|
case "$state" in
|
|
disabled-or-default)
|
|
echo "remote_practice_runtime_schema_bootstrap=false/default $remote_ssh:$remote_service"
|
|
;;
|
|
enabled)
|
|
fail "remote AIHR_PRACTICE_RUNTIME_SCHEMA_BOOTSTRAP is enabled; disable it and apply formal SQL migrations"
|
|
;;
|
|
unreadable:*)
|
|
fail "remote runtime schema guard cannot inspect ${state#unreadable:}; grant read access or unset the file before release"
|
|
;;
|
|
*)
|
|
fail "remote runtime schema guard returned an unexpected state: $state"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
require_remote_schema() {
|
|
local remote_ssh="${RELEASE_REMOTE_SSH:-YCWY}"
|
|
local remote_db="${RELEASE_REMOTE_DB_NAME:-ry-vue}"
|
|
[[ "$remote_db" =~ ^[A-Za-z0-9_-]+$ ]] || fail "remote database name is invalid: $remote_db"
|
|
|
|
local missing
|
|
missing="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT required.table_name
|
|
FROM (
|
|
SELECT 'aihr_prompt_template' AS table_name
|
|
UNION ALL SELECT 'aihr_practice_scenario'
|
|
UNION ALL SELECT 'aihr_practice_rubric'
|
|
UNION ALL SELECT 'aihr_practice_rubric_dimension'
|
|
UNION ALL SELECT 'aihr_practice_audio'
|
|
UNION ALL SELECT 'aihr_practice_audio_upload'
|
|
UNION ALL SELECT 'aihr_practice_calibration'
|
|
UNION ALL SELECT 'aihr_practice_help_event'
|
|
UNION ALL SELECT 'aihr_knowledge_gap'
|
|
UNION ALL SELECT 'aihr_knowledge_answer_feedback'
|
|
UNION ALL SELECT 'aihr_knowledge_space_grant'
|
|
UNION ALL SELECT 'aihr_knowledge_app'
|
|
UNION ALL SELECT 'aihr_knowledge_app_space'
|
|
UNION ALL SELECT 'aihr_knowledge_category'
|
|
UNION ALL SELECT 'aihr_knowledge_query_log'
|
|
UNION ALL SELECT 'aihr_knowledge_conversation'
|
|
UNION ALL SELECT 'aihr_knowledge_admin_audit'
|
|
UNION ALL SELECT 'aihr_knowledge_source_governance'
|
|
UNION ALL SELECT 'aihr_agent_run'
|
|
UNION ALL SELECT 'aihr_learning_question'
|
|
UNION ALL SELECT 'aihr_practice_question_feedback'
|
|
UNION ALL SELECT 'aihr_onboard_exam'
|
|
UNION ALL SELECT 'aihr_onboard_exam_target'
|
|
UNION ALL SELECT 'aihr_onboard_exam_question'
|
|
UNION ALL SELECT 'aihr_exam_question_draw'
|
|
UNION ALL SELECT 'aihr_onboard_exam_attempt'
|
|
UNION ALL SELECT 'aihr_onboard_exam_answer'
|
|
UNION ALL SELECT 'aihr_web_search_provider'
|
|
UNION ALL SELECT 'aihr_web_ai_secret'
|
|
UNION ALL SELECT 'aihr_web_ai_query_audit'
|
|
UNION ALL SELECT 'aihr_community_question'
|
|
UNION ALL SELECT 'aihr_community_answer'
|
|
UNION ALL SELECT 'aihr_incentive_rule'
|
|
UNION ALL SELECT 'aihr_points_ledger'
|
|
UNION ALL SELECT 'aihr_candidate_material'
|
|
UNION ALL SELECT 'aihr_interview_result'
|
|
UNION ALL SELECT 'aihr_candidate_employee_link'
|
|
UNION ALL SELECT 'aihr_position_responsibility'
|
|
UNION ALL SELECT 'aihr_sop_applicability'
|
|
UNION ALL SELECT 'aihr_onboard_task'
|
|
UNION ALL SELECT 'aihr_qualification_gate'
|
|
UNION ALL SELECT 'aihr_org_directory'
|
|
UNION ALL SELECT 'aihr_tenant_org_binding'
|
|
UNION ALL SELECT 'aihr_memory_candidate'
|
|
UNION ALL SELECT 'aihr_assistant_capture'
|
|
UNION ALL SELECT 'aihr_assistant_capture_status_log'
|
|
UNION ALL SELECT 'aihr_daily_work_result'
|
|
UNION ALL SELECT 'aihr_service_memory'
|
|
UNION ALL SELECT 'aihr_service_memory_version'
|
|
UNION ALL SELECT 'aihr_work_report'
|
|
UNION ALL SELECT 'aihr_broadcast_message'
|
|
UNION ALL SELECT 'aihr_broadcast_read'
|
|
UNION ALL SELECT 'aihr_broadcast_version'
|
|
UNION ALL SELECT 'aihr_broadcast_target_rule'
|
|
UNION ALL SELECT 'aihr_broadcast_target_recipient'
|
|
UNION ALL SELECT 'aihr_broadcast_attachment'
|
|
UNION ALL SELECT 'aihr_direct_feedback'
|
|
UNION ALL SELECT 'aihr_personal_space'
|
|
UNION ALL SELECT 'aihr_personal_item'
|
|
UNION ALL SELECT 'aihr_personal_fragment'
|
|
UNION ALL SELECT 'aihr_personal_chat_session'
|
|
UNION ALL SELECT 'aihr_personal_chat_message'
|
|
UNION ALL SELECT 'aihr_personal_cleanup_job'
|
|
UNION ALL SELECT 'aihr_personal_ocr_job'
|
|
UNION ALL SELECT 'aihr_personal_ocr_page'
|
|
UNION ALL SELECT 'aihr_personal_export_task'
|
|
UNION ALL SELECT 'aihr_personal_publish_request'
|
|
UNION ALL SELECT 'aihr_knowledge_rollout_scope'
|
|
UNION ALL SELECT 'aihr_knowledge_rollout_transition'
|
|
UNION ALL SELECT 'aihr_review_assistance'
|
|
UNION ALL SELECT 'aihr_review_batch'
|
|
UNION ALL SELECT 'aihr_review_batch_item'
|
|
UNION ALL SELECT 'aihr_knowledge_generation'
|
|
UNION ALL SELECT 'aihr_generation_version'
|
|
UNION ALL SELECT 'aihr_version_diff'
|
|
UNION ALL SELECT 'aihr_version_rollback'
|
|
UNION ALL SELECT 'aihr_deletion_request'
|
|
UNION ALL SELECT 'aihr_reconciliation_run'
|
|
UNION ALL SELECT 'aihr_reconciliation_issue'
|
|
UNION ALL SELECT 'aihr_migration_run'
|
|
UNION ALL SELECT 'aihr_migration_batch'
|
|
UNION ALL SELECT 'aihr_migration_dead_letter'
|
|
UNION ALL SELECT 'aihr_shadow_comparison'
|
|
UNION ALL SELECT 'aihr_activation_gate'
|
|
UNION ALL SELECT 'aihr_generation_activation'
|
|
) required
|
|
LEFT JOIN information_schema.tables actual
|
|
ON actual.table_schema = DATABASE() AND actual.table_name = required.table_name
|
|
WHERE actual.table_name IS NULL
|
|
ORDER BY required.table_name;
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote schema check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ -z "$missing" ]] || fail "remote schema missing required tables: $(printf '%s' "$missing" | tr '\n' ', ' | sed 's/, $//')"
|
|
local personal_oss_config
|
|
personal_oss_config="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
bucket_name, '|', access_policy, '|', status, '|',
|
|
CASE WHEN access_key <> '' AND secret_key <> '' THEN 'configured' ELSE 'credentials-missing' END
|
|
)
|
|
FROM sys_oss_config
|
|
WHERE tenant_id = '000000' AND config_key = 'personal-minio';
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote personal OSS configuration check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$personal_oss_config" = "ruoyi-personal|0|1|configured" ]] \
|
|
|| fail "remote personal OSS configuration is invalid: expected private ruoyi-personal configuration; got ${personal_oss_config:-missing}"
|
|
local missing_work_report_columns
|
|
missing_work_report_columns="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT required.column_name
|
|
FROM (
|
|
SELECT 'request_key' AS column_name
|
|
UNION ALL SELECT 'request_hash'
|
|
) required
|
|
LEFT JOIN information_schema.columns actual
|
|
ON actual.table_schema = DATABASE()
|
|
AND actual.table_name = 'aihr_work_report'
|
|
AND actual.column_name = required.column_name
|
|
WHERE actual.column_name IS NULL
|
|
ORDER BY required.column_name;
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote work-report schema check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ -z "$missing_work_report_columns" ]] \
|
|
|| fail "remote work-report schema missing required columns: $(printf '%s' "$missing_work_report_columns" | tr '\n' ', ' | sed 's/, $//')"
|
|
local missing_practice_curriculum_columns
|
|
missing_practice_curriculum_columns="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(required.table_name, '.', required.column_name)
|
|
FROM (
|
|
SELECT 'aihr_practice_scenario' AS table_name, 'growth_level' AS column_name
|
|
UNION ALL SELECT 'aihr_practice_scenario', 'competency_code'
|
|
UNION ALL SELECT 'aihr_practice_scenario', 'collaboration_positions'
|
|
UNION ALL SELECT 'aihr_practice_scenario', 'review_status'
|
|
UNION ALL SELECT 'aihr_practice_scenario', 'risk_level'
|
|
UNION ALL SELECT 'aihr_practice_scenario', 'curriculum_version'
|
|
UNION ALL SELECT 'aihr_practice_scenario', 'reviewer'
|
|
UNION ALL SELECT 'aihr_practice_scenario', 'reviewer_user_id'
|
|
UNION ALL SELECT 'aihr_practice_scenario', 'reviewed_time'
|
|
UNION ALL SELECT 'aihr_practice_scenario', 'second_reviewer'
|
|
UNION ALL SELECT 'aihr_practice_scenario', 'second_reviewer_user_id'
|
|
UNION ALL SELECT 'aihr_practice_scenario', 'second_reviewed_time'
|
|
UNION ALL SELECT 'aihr_practice_session', 'position_snapshot'
|
|
UNION ALL SELECT 'aihr_practice_session', 'project_type_snapshot'
|
|
UNION ALL SELECT 'aihr_practice_session', 'growth_level'
|
|
UNION ALL SELECT 'aihr_practice_session', 'competency_code'
|
|
UNION ALL SELECT 'aihr_practice_session', 'collaboration_positions'
|
|
UNION ALL SELECT 'aihr_practice_session', 'curriculum_version'
|
|
UNION ALL SELECT 'aihr_practice_session', 'growth_confirmation_level'
|
|
UNION ALL SELECT 'aihr_practice_session', 'growth_confirmed_by'
|
|
UNION ALL SELECT 'aihr_practice_session', 'growth_confirmed_time'
|
|
) required
|
|
LEFT JOIN information_schema.columns actual
|
|
ON actual.table_schema = DATABASE()
|
|
AND actual.table_name = required.table_name
|
|
AND actual.column_name = required.column_name
|
|
WHERE actual.column_name IS NULL
|
|
ORDER BY required.table_name, required.column_name;
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote practice-curriculum schema check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ -z "$missing_practice_curriculum_columns" ]] \
|
|
|| fail "remote practice-curriculum schema missing required columns: $(printf '%s' "$missing_practice_curriculum_columns" | tr '\n' ', ' | sed 's/, $//')"
|
|
local practice_assignment_request_unique_index
|
|
practice_assignment_request_unique_index="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
COALESCE(MIN(non_unique), -1), '|',
|
|
COALESCE(GROUP_CONCAT(column_name ORDER BY seq_in_index), ''), '|',
|
|
COALESCE(SUM(sub_part IS NOT NULL), 0)
|
|
)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_practice_assignment'
|
|
AND index_name = 'uk_aihr_assignment_request';
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote practice-assignment idempotency-index check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$practice_assignment_request_unique_index" = "0|tenant_id,request_key,ext_party_id|0" ]] \
|
|
|| fail "remote practice-assignment idempotency index invalid: expected unique full columns tenant_id,request_key,ext_party_id; got ${practice_assignment_request_unique_index:-missing}"
|
|
local practice_assignment_recent_content_index
|
|
practice_assignment_recent_content_index="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
COALESCE(MIN(non_unique), -1), '|',
|
|
COALESCE(GROUP_CONCAT(column_name ORDER BY seq_in_index), ''), '|',
|
|
COALESCE(SUM(sub_part IS NOT NULL), 0)
|
|
)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_practice_assignment'
|
|
AND index_name = 'idx_aihr_assignment_recent_content';
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote practice-assignment recent-content-index check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$practice_assignment_recent_content_index" = "1|tenant_id,ext_party_id,source,content_hash,create_time|0" ]] \
|
|
|| fail "remote practice-assignment recent-content index invalid: expected full columns tenant_id,ext_party_id,source,content_hash,create_time; got ${practice_assignment_recent_content_index:-missing}"
|
|
local agent_schema_contract
|
|
agent_schema_contract="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
(SELECT COUNT(*) FROM information_schema.columns
|
|
WHERE table_schema = DATABASE() AND table_name = 'aihr_agent_run'), '|',
|
|
(SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE() AND table_name = 'aihr_agent_run'
|
|
AND index_name = 'uk_aihr_agent_run'), '|',
|
|
(SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE() AND table_name = 'aihr_agent_run'
|
|
AND index_name = 'idx_aihr_agent_run_user')
|
|
);
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote Agent audit schema check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$agent_schema_contract" = "15|run_id|tenant_id,user_id,create_time" ]] \
|
|
|| fail "remote Agent audit schema is invalid: got ${agent_schema_contract:-missing}"
|
|
local missing_knowledge_category_columns
|
|
missing_knowledge_category_columns="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(required.table_name, '.', required.column_name)
|
|
FROM (
|
|
SELECT 'aihr_knowledge_category' AS table_name, 'tenant_id' AS column_name
|
|
UNION ALL SELECT 'aihr_knowledge_category', 'knowledge_id'
|
|
UNION ALL SELECT 'aihr_knowledge_category', 'code'
|
|
UNION ALL SELECT 'aihr_knowledge_category', 'name'
|
|
UNION ALL SELECT 'aihr_knowledge_category', 'status'
|
|
UNION ALL SELECT 'aihr_knowledge_category', 'sort_order'
|
|
UNION ALL SELECT 'aihr_knowledge_attach', 'category_id'
|
|
) required
|
|
LEFT JOIN information_schema.columns actual
|
|
ON actual.table_schema = DATABASE()
|
|
AND actual.table_name = required.table_name
|
|
AND actual.column_name = required.column_name
|
|
WHERE actual.column_name IS NULL
|
|
ORDER BY required.table_name, required.column_name;
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote knowledge-category schema check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ -z "$missing_knowledge_category_columns" ]] \
|
|
|| fail "remote knowledge-category schema missing required columns: $(printf '%s' "$missing_knowledge_category_columns" | tr '\n' ', ' | sed 's/, $//')"
|
|
local knowledge_attach_category_index
|
|
knowledge_attach_category_index="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
MIN(non_unique), '|',
|
|
COALESCE(GROUP_CONCAT(column_name ORDER BY seq_in_index), ''), '|',
|
|
SUM(sub_part IS NOT NULL)
|
|
)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_knowledge_attach'
|
|
AND index_name = 'idx_aihr_knowledge_attach_category';
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote knowledge-attachment category-index check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$knowledge_attach_category_index" = "1|tenant_id,knowledge_id,category_id|0" ]] \
|
|
|| fail "remote knowledge-attachment category index invalid: expected full columns tenant_id,knowledge_id,category_id; got ${knowledge_attach_category_index:-missing}"
|
|
local knowledge_category_code_index
|
|
knowledge_category_code_index="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
MIN(non_unique), '|',
|
|
COALESCE(GROUP_CONCAT(column_name ORDER BY seq_in_index), ''), '|',
|
|
SUM(sub_part IS NOT NULL)
|
|
)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_knowledge_category'
|
|
AND index_name = 'uk_aihr_knowledge_category_code';
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote knowledge-category unique-index check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$knowledge_category_code_index" = "0|tenant_id,knowledge_id,code|0" ]] \
|
|
|| fail "remote knowledge-category unique index invalid: expected unique full columns tenant_id,knowledge_id,code; got ${knowledge_category_code_index:-missing}"
|
|
local work_report_unique_index
|
|
work_report_unique_index="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
MIN(non_unique), '|',
|
|
COALESCE(GROUP_CONCAT(column_name ORDER BY seq_in_index), ''), '|',
|
|
SUM(sub_part IS NOT NULL)
|
|
)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_work_report'
|
|
AND index_name = 'uk_aihr_work_report_request';
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote work-report unique-index check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$work_report_unique_index" = "0|tenant_id,submitter_user_id,request_key|0" ]] \
|
|
|| fail "remote work-report unique index invalid: expected unique full columns tenant_id,submitter_user_id,request_key; got ${work_report_unique_index:-missing}"
|
|
local feedback_question_bridge_contract
|
|
feedback_question_bridge_contract="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
(SELECT COUNT(*) FROM information_schema.columns
|
|
WHERE table_schema = DATABASE() AND table_name = 'aihr_knowledge_answer_feedback'
|
|
AND column_name IN ('submitter_user_id','request_id')), '|',
|
|
(SELECT COUNT(*) FROM information_schema.columns
|
|
WHERE table_schema = DATABASE() AND table_name = 'aihr_community_question'
|
|
AND column_name IN ('source_type','source_request_id','submit_idempotency_key')), '|',
|
|
COALESCE((SELECT CONCAT(MIN(non_unique), ':', GROUP_CONCAT(column_name ORDER BY seq_in_index), ':', SUM(sub_part IS NOT NULL))
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE() AND table_name = 'aihr_knowledge_answer_feedback'
|
|
AND index_name = 'uk_aihr_answer_feedback_user_request'), ''), '|',
|
|
COALESCE((SELECT CONCAT(MIN(non_unique), ':', GROUP_CONCAT(column_name ORDER BY seq_in_index), ':', SUM(sub_part IS NOT NULL))
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE() AND table_name = 'aihr_community_question'
|
|
AND index_name = 'uk_aihr_community_question_source'), ''), '|',
|
|
COALESCE((SELECT CONCAT(MIN(non_unique), ':', GROUP_CONCAT(column_name ORDER BY seq_in_index), ':', SUM(sub_part IS NOT NULL))
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE() AND table_name = 'aihr_community_question'
|
|
AND index_name = 'uk_aihr_community_question_submit_key'), '')
|
|
);
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote feedback-question bridge schema check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$feedback_question_bridge_contract" = "2|3|0:tenant_id,submitter_user_id,request_id,fragment_id:0|0:tenant_id,author_user_id,source_type,source_request_id:0|0:tenant_id,author_user_id,submit_idempotency_key:0" ]] \
|
|
|| fail "remote feedback-question bridge contract invalid: got ${feedback_question_bridge_contract:-missing}"
|
|
local learning_task_exam_bank_contract
|
|
learning_task_exam_bank_contract="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
(SELECT COUNT(*) FROM information_schema.columns
|
|
WHERE table_schema = DATABASE() AND table_name = 'aihr_learning_question'
|
|
AND column_name IN ('exam_format','exam_options_json','exam_answers_json','exam_explanation','exam_enabled','review_status','review_note','reviewed_by','reviewed_time')), '|',
|
|
COALESCE((SELECT CONCAT(MIN(non_unique), ':', GROUP_CONCAT(column_name ORDER BY seq_in_index), ':', SUM(sub_part IS NOT NULL))
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE() AND table_name = 'aihr_learning_question'
|
|
AND index_name = 'idx_aihr_learning_question_exam_pick'), ''), '|',
|
|
(SELECT COUNT(*) FROM information_schema.columns
|
|
WHERE table_schema = DATABASE() AND table_name = 'aihr_onboard_exam_question'
|
|
AND column_name IN ('source_question_id','source_question_code','source_content_version','source_content_hash')), '|',
|
|
(SELECT COUNT(*) FROM information_schema.columns
|
|
WHERE table_schema = DATABASE() AND table_name = 'aihr_onboard_task'
|
|
AND column_name IN ('material_type','material_attachment_id','material_title','material_version','progress_percent','completion_rule','exam_id','recurrence_type','recurrence_series_key','dispatch_request_key')), '|',
|
|
COALESCE((SELECT CONCAT(MIN(non_unique), ':', GROUP_CONCAT(column_name ORDER BY seq_in_index), ':', SUM(sub_part IS NOT NULL))
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE() AND table_name = 'aihr_onboard_task'
|
|
AND index_name = 'uk_aihr_onboard_task_dispatch'), ''), '|',
|
|
COALESCE((SELECT CONCAT(MIN(non_unique), ':', GROUP_CONCAT(column_name ORDER BY seq_in_index), ':', SUM(sub_part IS NOT NULL))
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE() AND table_name = 'aihr_exam_question_draw'
|
|
AND index_name = 'uk_aihr_exam_question_draw_request'), '')
|
|
);
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote learning-task/question-bank schema check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$learning_task_exam_bank_contract" = "9|1:tenant_id,position_code,review_status,exam_enabled,enabled,id:0|4|10|0:tenant_id,ext_party_id,dispatch_request_key:0|0:tenant_id,owner_ext_party_id,request_key:0" ]] \
|
|
|| fail "remote learning-task/question-bank contract invalid: got ${learning_task_exam_bank_contract:-missing}"
|
|
local missing_broadcast_publish_columns
|
|
missing_broadcast_publish_columns="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT required.column_name
|
|
FROM (
|
|
SELECT 'publish_request_key' AS column_name
|
|
UNION ALL SELECT 'publish_request_hash'
|
|
UNION ALL SELECT 'withdraw_reason'
|
|
) required
|
|
LEFT JOIN information_schema.columns actual
|
|
ON actual.table_schema = DATABASE()
|
|
AND actual.table_name = 'aihr_broadcast_message'
|
|
AND actual.column_name = required.column_name
|
|
WHERE actual.column_name IS NULL
|
|
ORDER BY required.column_name;
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote broadcast publish-audit schema check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ -z "$missing_broadcast_publish_columns" ]] \
|
|
|| fail "remote broadcast message schema missing required columns: $(printf '%s' "$missing_broadcast_publish_columns" | tr '\n' ', ' | sed 's/, $//')"
|
|
local broadcast_publish_unique_index
|
|
broadcast_publish_unique_index="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
MIN(non_unique), '|',
|
|
COALESCE(GROUP_CONCAT(column_name ORDER BY seq_in_index), ''), '|',
|
|
SUM(sub_part IS NOT NULL)
|
|
)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_broadcast_message'
|
|
AND index_name = 'uk_aihr_broadcast_message_publish_request';
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote broadcast publish unique-index check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$broadcast_publish_unique_index" = "0|tenant_id,published_by,publish_request_key|0" ]] \
|
|
|| fail "remote broadcast publish unique index invalid: expected unique full columns tenant_id,published_by,publish_request_key; got ${broadcast_publish_unique_index:-missing}"
|
|
local broadcast_read_unique_index
|
|
broadcast_read_unique_index="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
MIN(non_unique), '|',
|
|
COALESCE(GROUP_CONCAT(column_name ORDER BY seq_in_index), ''), '|',
|
|
SUM(sub_part IS NOT NULL)
|
|
)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_broadcast_read'
|
|
AND index_name = 'uk_aihr_broadcast_read';
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote broadcast-read unique-index check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$broadcast_read_unique_index" = "0|tenant_id,message_id,user_id|0" ]] \
|
|
|| fail "remote broadcast-read unique index invalid: expected unique full columns tenant_id,message_id,user_id; got ${broadcast_read_unique_index:-missing}"
|
|
local broadcast_version_unique_index
|
|
broadcast_version_unique_index="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
MIN(non_unique), '|',
|
|
COALESCE(GROUP_CONCAT(column_name ORDER BY seq_in_index), ''), '|',
|
|
SUM(sub_part IS NOT NULL)
|
|
)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_broadcast_version'
|
|
AND index_name = 'uk_aihr_broadcast_version';
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote broadcast-version unique-index check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$broadcast_version_unique_index" = "0|tenant_id,message_id,version|0" ]] \
|
|
|| fail "remote broadcast-version unique index invalid: expected unique full columns tenant_id,message_id,version; got ${broadcast_version_unique_index:-missing}"
|
|
local broadcast_targeting_column_contract
|
|
broadcast_targeting_column_contract="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(column_name, '|', column_type, '|', is_nullable, '|', COALESCE(column_default, '<NULL>'))
|
|
FROM information_schema.columns
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_broadcast_message'
|
|
AND column_name IN ('required_read', 'target_payload_hash')
|
|
ORDER BY FIELD(column_name, 'required_read', 'target_payload_hash');
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote broadcast M1 column check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$broadcast_targeting_column_contract" = $'required_read|tinyint(1)|NO|0\ntarget_payload_hash|char(64)|YES|<NULL>' ]] \
|
|
|| fail "remote broadcast M1 column contract invalid: expected required_read tinyint(1) NOT NULL DEFAULT 0 and nullable target_payload_hash char(64); got ${broadcast_targeting_column_contract:-missing}"
|
|
local broadcast_target_table_contract
|
|
broadcast_target_table_contract="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(table_name, '.', column_name, '|', column_type, '|', is_nullable)
|
|
FROM information_schema.columns
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name IN ('aihr_broadcast_target_rule', 'aihr_broadcast_target_recipient')
|
|
ORDER BY FIELD(table_name, 'aihr_broadcast_target_rule', 'aihr_broadcast_target_recipient'), ordinal_position;
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote broadcast target-table contract check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$broadcast_target_table_contract" = $'aihr_broadcast_target_rule.id|bigint|NO\naihr_broadcast_target_rule.tenant_id|varchar(20)|NO\naihr_broadcast_target_rule.message_id|bigint|NO\naihr_broadcast_target_rule.target_type|varchar(20)|NO\naihr_broadcast_target_rule.target_value|varchar(100)|NO\naihr_broadcast_target_rule.create_time|datetime|NO\naihr_broadcast_target_recipient.id|bigint|NO\naihr_broadcast_target_recipient.tenant_id|varchar(20)|NO\naihr_broadcast_target_recipient.message_id|bigint|NO\naihr_broadcast_target_recipient.recipient_key|varchar(180)|NO\naihr_broadcast_target_recipient.subject_ref|varchar(100)|NO\naihr_broadcast_target_recipient.user_id|bigint|YES\naihr_broadcast_target_recipient.target_status|varchar(20)|NO\naihr_broadcast_target_recipient.target_reason|varchar(100)|NO\naihr_broadcast_target_recipient.create_time|datetime|NO\naihr_broadcast_target_recipient.update_time|datetime|NO' ]] \
|
|
|| fail "remote broadcast target-table contract invalid: expected server-written recipient identity, status, reason, and timing fields; got ${broadcast_target_table_contract:-missing}"
|
|
local broadcast_target_rule_unique_index
|
|
broadcast_target_rule_unique_index="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
MIN(non_unique), '|',
|
|
COALESCE(GROUP_CONCAT(column_name ORDER BY seq_in_index), ''), '|',
|
|
SUM(sub_part IS NOT NULL)
|
|
)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_broadcast_target_rule'
|
|
AND index_name = 'uk_aihr_broadcast_target_rule';
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote broadcast target-rule unique-index check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$broadcast_target_rule_unique_index" = "0|tenant_id,message_id,target_type,target_value|0" ]] \
|
|
|| fail "remote broadcast target-rule unique index invalid: expected unique full columns tenant_id,message_id,target_type,target_value; got ${broadcast_target_rule_unique_index:-missing}"
|
|
local broadcast_target_recipient_unique_index
|
|
broadcast_target_recipient_unique_index="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
MIN(non_unique), '|',
|
|
COALESCE(GROUP_CONCAT(column_name ORDER BY seq_in_index), ''), '|',
|
|
SUM(sub_part IS NOT NULL)
|
|
)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_broadcast_target_recipient'
|
|
AND index_name = 'uk_aihr_broadcast_target_recipient';
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote broadcast target-recipient unique-index check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$broadcast_target_recipient_unique_index" = "0|tenant_id,message_id,recipient_key|0" ]] \
|
|
|| fail "remote broadcast target-recipient unique index invalid: expected unique full columns tenant_id,message_id,recipient_key; got ${broadcast_target_recipient_unique_index:-missing}"
|
|
local direct_feedback_contract
|
|
direct_feedback_contract="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
COUNT(DISTINCT c.column_name), '|',
|
|
MIN(s.non_unique), '|',
|
|
COALESCE(GROUP_CONCAT(DISTINCT s.column_name ORDER BY s.seq_in_index), ''), '|',
|
|
SUM(s.sub_part IS NOT NULL)
|
|
)
|
|
FROM information_schema.columns c
|
|
LEFT JOIN information_schema.statistics s
|
|
ON s.table_schema = c.table_schema
|
|
AND s.table_name = c.table_name
|
|
AND s.index_name = 'uk_aihr_direct_feedback_submit'
|
|
WHERE c.table_schema = DATABASE()
|
|
AND c.table_name = 'aihr_direct_feedback'
|
|
AND c.column_name IN ('tenant_id','channel_code','sender_user_id','anonymous_flag','content','status','submit_request_key','submit_request_hash','reply_content','replied_by','replied_time');
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote direct-feedback schema check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$direct_feedback_contract" = "11|0|tenant_id,sender_user_id,submit_request_key|0" ]] \
|
|
|| fail "remote direct-feedback contract invalid: expected 11 business columns and unique tenant_id,sender_user_id,submit_request_key; got ${direct_feedback_contract:-missing}"
|
|
local direct_role_count
|
|
direct_role_count="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT COUNT(DISTINCT role_key)
|
|
FROM sys_role
|
|
WHERE tenant_id = '000000'
|
|
AND del_flag = '0'
|
|
AND role_key IN ('direct_president','direct_finance','direct_hr','direct_audit','direct_operations');
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote direct-feedback role check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$direct_role_count" = "5" ]] \
|
|
|| fail "remote direct-feedback roles incomplete: expected 5/5; got ${direct_role_count:-0}/5"
|
|
local broadcast_attachment_contract
|
|
broadcast_attachment_contract="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
(SELECT COUNT(*) FROM information_schema.columns
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_broadcast_attachment'
|
|
AND column_name IN ('tenant_id','message_id','oss_id','file_name','file_size','content_type','status','extracted_text','summary','insight_status','insights_json','insight_version','error_message','uploaded_by')),
|
|
'|',
|
|
(SELECT COUNT(*) FROM information_schema.columns
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_broadcast_message'
|
|
AND column_name IN ('attachment_id','visibility_mode','allow_download')),
|
|
'|',
|
|
COALESCE((SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_broadcast_message'
|
|
AND index_name = 'idx_aihr_broadcast_message_attachment'), ''),
|
|
'|',
|
|
COALESCE((SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_broadcast_attachment'
|
|
AND index_name = 'idx_aihr_broadcast_attachment_insight_queue'), ''),
|
|
'|',
|
|
(SELECT COUNT(*) FROM information_schema.tables
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name IN ('aihr_broadcast_attachment_topic_tag','aihr_broadcast_attachment_access_audit'))
|
|
);
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote broadcast-attachment schema check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$broadcast_attachment_contract" = "14|3|tenant_id,attachment_id|insight_status,status,update_time|2" ]] \
|
|
|| fail "remote broadcast-attachment contract invalid: expected attachment columns, delivery controls, topic/access tables, and required indexes; got ${broadcast_attachment_contract:-missing}"
|
|
local missing_work_assistant_columns
|
|
missing_work_assistant_columns="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(required.table_name, '.', required.column_name)
|
|
FROM (
|
|
SELECT 'aihr_memory_candidate' AS table_name, 'work_date' AS column_name
|
|
UNION ALL SELECT 'aihr_memory_candidate', 'source_snapshot_json'
|
|
UNION ALL SELECT 'aihr_assistant_capture', 'work_date'
|
|
UNION ALL SELECT 'aihr_assistant_capture', 'source_snapshot_json'
|
|
UNION ALL SELECT 'aihr_assistant_capture', 'business_status'
|
|
UNION ALL SELECT 'aihr_knowledge_conversation', 'project_code'
|
|
UNION ALL SELECT 'aihr_knowledge_conversation', 'broadcast_message_id'
|
|
UNION ALL SELECT 'aihr_daily_work_result', 'content_hash'
|
|
) required
|
|
LEFT JOIN information_schema.columns actual
|
|
ON actual.table_schema = DATABASE()
|
|
AND actual.table_name = required.table_name
|
|
AND actual.column_name = required.column_name
|
|
WHERE actual.column_name IS NULL
|
|
ORDER BY required.table_name, required.column_name;
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote work-assistant schema check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ -z "$missing_work_assistant_columns" ]] \
|
|
|| fail "remote work-assistant schema missing required columns: $(printf '%s' "$missing_work_assistant_columns" | tr '\n' ', ' | sed 's/, $//')"
|
|
|
|
local missing_knowledge_lifecycle_columns
|
|
missing_knowledge_lifecycle_columns="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(required.table_name, '.', required.column_name)
|
|
FROM (
|
|
SELECT 'aihr_data_asset' AS table_name, 'published_version_id' AS column_name
|
|
UNION ALL SELECT 'aihr_data_asset', 'candidate_version_id'
|
|
UNION ALL SELECT 'aihr_data_asset', 'published_time'
|
|
UNION ALL SELECT 'aihr_data_asset', 'retention_class'
|
|
UNION ALL SELECT 'aihr_data_asset', 'legal_hold'
|
|
UNION ALL SELECT 'aihr_data_asset', 'delete_state'
|
|
UNION ALL SELECT 'aihr_data_version', 'version_status'
|
|
UNION ALL SELECT 'aihr_data_version', 'revision_of_version_id'
|
|
UNION ALL SELECT 'aihr_data_version', 'revision_reason'
|
|
UNION ALL SELECT 'aihr_chunk_revision', 'index_generation'
|
|
UNION ALL SELECT 'aihr_chunk_revision', 'vector_point_id'
|
|
UNION ALL SELECT 'aihr_index_outbox', 'index_generation'
|
|
UNION ALL SELECT 'aihr_knowledge_generation', 'embedding_model'
|
|
UNION ALL SELECT 'aihr_knowledge_generation', 'embedding_dimension'
|
|
UNION ALL SELECT 'aihr_migration_run', 'verified_dry_run_id'
|
|
) required
|
|
LEFT JOIN information_schema.columns actual
|
|
ON actual.table_schema = DATABASE()
|
|
AND actual.table_name = required.table_name
|
|
AND actual.column_name = required.column_name
|
|
WHERE actual.column_name IS NULL
|
|
ORDER BY required.table_name, required.column_name;
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote knowledge-lifecycle schema check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ -z "$missing_knowledge_lifecycle_columns" ]] \
|
|
|| fail "remote knowledge-lifecycle schema missing required columns: $(printf '%s' "$missing_knowledge_lifecycle_columns" | tr '\n' ', ' | sed 's/, $//')"
|
|
|
|
local knowledge_lifecycle_index_contract
|
|
knowledge_lifecycle_index_contract="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
COALESCE((SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index)
|
|
FROM information_schema.statistics WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_data_asset' AND index_name = 'idx_aihr_data_asset_published_version'), 'missing'), '|',
|
|
COALESCE((SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index)
|
|
FROM information_schema.statistics WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_data_asset' AND index_name = 'idx_aihr_data_asset_candidate_version'), 'missing'), '|',
|
|
COALESCE((SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index)
|
|
FROM information_schema.statistics WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_data_version' AND index_name = 'idx_aihr_data_version_status'), 'missing'), '|',
|
|
COALESCE((SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index)
|
|
FROM information_schema.statistics WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_chunk_revision' AND index_name = 'idx_aihr_chunk_revision_generation'), 'missing'), '|',
|
|
COALESCE((SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index)
|
|
FROM information_schema.statistics WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_migration_run' AND index_name = 'idx_aihr_migration_verified_dry_run'), 'missing')
|
|
);
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote knowledge-lifecycle index check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$knowledge_lifecycle_index_contract" = "tenant_id,published_version_id|tenant_id,candidate_version_id|tenant_id,version_status,id|tenant_id,asset_id,version_id,index_generation|tenant_id,verified_dry_run_id" ]] \
|
|
|| fail "remote knowledge-lifecycle index contract is invalid: got ${knowledge_lifecycle_index_contract:-missing}"
|
|
|
|
local media_reprocess_contract
|
|
media_reprocess_contract="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_db" <<'REMOTE'
|
|
set -euo pipefail
|
|
db="$1"
|
|
mysql --batch --skip-column-names --connect-timeout=5 "$db" <<'SQL'
|
|
SELECT CONCAT(
|
|
COALESCE((
|
|
SELECT column_name
|
|
FROM information_schema.columns
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_knowledge_upload_item'
|
|
AND column_name = 'source_attach_id'
|
|
), 'missing'), '|',
|
|
COALESCE((
|
|
SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'aihr_knowledge_upload_item'
|
|
AND index_name = 'idx_aihr_upload_item_source'
|
|
), 'missing')
|
|
);
|
|
SQL
|
|
REMOTE
|
|
)" || fail "remote media-reprocess schema check failed: $remote_ssh:$remote_db"
|
|
|
|
[[ "$media_reprocess_contract" = "source_attach_id|tenant_id,source_attach_id,id" ]] \
|
|
|| fail "remote media-reprocess schema is invalid: got ${media_reprocess_contract:-missing}"
|
|
echo "remote_schema=82/82 $remote_ssh:$remote_db"
|
|
echo "remote_media_reprocess_contract=1/1 $remote_ssh:$remote_db"
|
|
echo "remote_work_report_idempotency=3/3 $remote_ssh:$remote_db"
|
|
echo "remote_learning_task_question_bank=6/6 $remote_ssh:$remote_db"
|
|
echo "remote_practice_curriculum_columns=13/13 $remote_ssh:$remote_db"
|
|
echo "remote_knowledge_category_columns=7/7 $remote_ssh:$remote_db"
|
|
echo "remote_broadcast_publish_audit_columns=3/3 $remote_ssh:$remote_db"
|
|
echo "remote_broadcast_publish_idempotency=3/3 $remote_ssh:$remote_db"
|
|
echo "remote_broadcast_read_idempotency=3/3 $remote_ssh:$remote_db"
|
|
echo "remote_broadcast_version_uniqueness=3/3 $remote_ssh:$remote_db"
|
|
echo "remote_broadcast_targeting_contract=2/2 $remote_ssh:$remote_db"
|
|
echo "remote_broadcast_target_table_contract=16/16 $remote_ssh:$remote_db"
|
|
echo "remote_broadcast_target_rule_uniqueness=4/4 $remote_ssh:$remote_db"
|
|
echo "remote_broadcast_target_recipient_uniqueness=3/3 $remote_ssh:$remote_db"
|
|
echo "remote_direct_feedback_contract=11/11 $remote_ssh:$remote_db"
|
|
echo "remote_direct_feedback_roles=5/5 $remote_ssh:$remote_db"
|
|
echo "remote_broadcast_attachment_contract=15/15 $remote_ssh:$remote_db"
|
|
echo "remote_broadcast_question_context=1/1 $remote_ssh:$remote_db"
|
|
echo "remote_work_assistant_columns=8/8 $remote_ssh:$remote_db"
|
|
echo "remote_knowledge_lifecycle_columns=13/13 $remote_ssh:$remote_db"
|
|
echo "remote_knowledge_lifecycle_indexes=5/5 $remote_ssh:$remote_db"
|
|
echo "remote_personal_oss_configuration=true $remote_ssh:$remote_db"
|
|
}
|
|
|
|
require_remote_backend_match() {
|
|
command -v ssh >/dev/null 2>&1 || fail "ssh is required for remote backend verification"
|
|
local remote_ssh="${RELEASE_REMOTE_SSH:-YCWY}"
|
|
local remote_backend_path="${RELEASE_REMOTE_BACKEND_PATH:-/opt/wygj/app/ruoyi-admin.jar}"
|
|
[[ "$remote_backend_path" =~ ^/[A-Za-z0-9._/-]+$ ]] || fail "remote backend path must be an absolute safe path: $remote_backend_path"
|
|
|
|
local remote_backend_jar_sha256
|
|
local local_backend_jar_sha256
|
|
local local_backend_module_jar
|
|
local local_backend_module_sha256
|
|
local remote_backend_module_sha_file
|
|
local remote_backend_module_sha256
|
|
|
|
remote_backend_jar_sha256="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" "sha256sum '$remote_backend_path'" | awk '{print $1}')" \
|
|
|| fail "remote backend hash check failed: $remote_ssh:$remote_backend_path"
|
|
[[ "$remote_backend_jar_sha256" =~ ^[0-9a-f]{64}$ ]] || fail "remote backend hash is invalid: $remote_ssh:$remote_backend_path"
|
|
local_backend_jar_sha256="$(sha256 "$backend_jar")"
|
|
local_backend_module_jar="$(mktemp)"
|
|
unzip -p "$backend_jar" "BOOT-INF/lib/$backend_module_jar_name" > "$local_backend_module_jar"
|
|
local_backend_module_sha256="$(normalized_jar_content_sha256 "$local_backend_module_jar")"
|
|
rm -f "$local_backend_module_jar"
|
|
|
|
remote_backend_module_sha_file="$(mktemp)"
|
|
ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_backend_path" "$backend_module_jar_name" > "$remote_backend_module_sha_file" <<'REMOTE'
|
|
set -euo pipefail
|
|
outer="$1"
|
|
module="$2"
|
|
nested="$(mktemp)"
|
|
manifest="$(mktemp)"
|
|
cleanup() {
|
|
rm -f "$nested" "$manifest"
|
|
}
|
|
trap cleanup EXIT
|
|
unzip -p "$outer" "BOOT-INF/lib/$module" > "$nested"
|
|
while IFS= read -r entry; do
|
|
case "$entry" in
|
|
*/|META-INF/*.SF|META-INF/*.RSA|META-INF/*.DSA) continue ;;
|
|
esac
|
|
printf '%s %s\n' "$(unzip -p "$nested" "$entry" | sha256sum | awk '{print $1}')" "$entry" >> "$manifest"
|
|
done < <(unzip -Z1 "$nested" | LC_ALL=C sort)
|
|
sha256sum "$manifest" | awk '{print $1}'
|
|
REMOTE
|
|
remote_backend_module_sha256="$(sed -n '1p' "$remote_backend_module_sha_file")"
|
|
rm -f "$remote_backend_module_sha_file"
|
|
[[ -n "$remote_backend_module_sha256" ]] || fail "remote backend module hash check returned no value: $remote_ssh:$remote_backend_path"
|
|
[[ "$remote_backend_module_sha256" =~ ^[0-9a-f]{64}$ ]] || fail "remote backend module hash is invalid: $remote_ssh:$remote_backend_path"
|
|
|
|
echo "remote_backend_jar_sha256=$remote_backend_jar_sha256"
|
|
echo "local_backend_jar_sha256=$local_backend_jar_sha256"
|
|
echo "backend_module=$backend_module_jar_name"
|
|
echo "remote_backend_module_sha256=$remote_backend_module_sha256"
|
|
echo "local_backend_module_sha256=$local_backend_module_sha256"
|
|
[[ "$remote_backend_module_sha256" == "$local_backend_module_sha256" ]] || fail "remote AIHR module does not match local build"
|
|
echo "remote_backend_module_match=true"
|
|
}
|
|
|
|
changed_files="$(git status --porcelain)"
|
|
[[ -z "$changed_files" ]] || fail "worktree has uncommitted changes; commit the release batch before publishing"
|
|
|
|
echo "commit=$(git rev-parse HEAD)"
|
|
echo "artifact_commit=$artifact_commit_sha"
|
|
echo "head_epoch=$head_epoch"
|
|
echo "worktree=clean"
|
|
if [[ "$require_static_artifacts" == "true" ]]; then
|
|
echo "frontend_index_sha256=$(sha256 "$frontend_index")"
|
|
echo "frontend_asset=$frontend_asset"
|
|
echo "frontend_asset_sha256=$(sha256 "$frontend_asset_path")"
|
|
echo "mobile_index_sha256=$(sha256 "$mobile_index")"
|
|
echo "mobile_asset=$mobile_asset"
|
|
echo "mobile_asset_sha256=$(sha256 "$mobile_asset_path")"
|
|
fi
|
|
if [[ "$require_backend_artifact" == "true" ]]; then
|
|
echo "backend_jar_sha256=$(sha256 "$backend_jar")"
|
|
fi
|
|
|
|
if [[ -n "${RELEASE_REMOTE_URL:-}" ]]; then
|
|
remote="${RELEASE_REMOTE_URL%/}"
|
|
curl -fsS --max-time 15 "$remote/" >/dev/null || fail "remote root check failed: $remote/"
|
|
echo "remote_root=200 $remote/"
|
|
require_remote_business_success "remote_tenant_list" "$remote/prod-api/auth/tenant/list"
|
|
require_remote_business_success "remote_mobile_home" "$remote/prod-api/api/aihr/mobile/home/user"
|
|
|
|
if [[ "$verify_aug1_readiness" == "true" ]]; then
|
|
AIHR_AUG1_REMOTE_SSH="${RELEASE_REMOTE_SSH:-YCWY}" \
|
|
AIHR_AUG1_REMOTE_SERVICE="${RELEASE_REMOTE_SERVICE:-wygj-aihr.service}" \
|
|
AIHR_AUG1_REMOTE_DB="${RELEASE_REMOTE_DB_NAME:-ry-vue}" \
|
|
AIHR_AUG1_TENANT_ID="${RELEASE_AUG1_TENANT_ID:-000000}" \
|
|
AIHR_AUG1_APPROVAL_PATH="${RELEASE_AUG1_APPROVAL_PATH:-$ROOT_DIR/docs/content-candidates/aug1-release-approval.json}" \
|
|
"$ROOT_DIR/scripts/verify-aug1-release-readiness.sh" --execute
|
|
fi
|
|
|
|
if [[ "$verify_remote_schema" == "true" ]]; then
|
|
require_remote_schema
|
|
fi
|
|
|
|
if [[ "$verify_remote_schema" == "true" || "$verify_remote_backend" == "true" ]]; then
|
|
require_remote_practice_schema_guard
|
|
fi
|
|
|
|
if [[ "$verify_remote_backend" == "true" ]]; then
|
|
require_remote_backend_match
|
|
fi
|
|
|
|
if [[ "$verify_remote_match" == "true" ]]; then
|
|
remote_frontend_asset="$(curl -fsS --max-time 15 "$remote/" | sed -nE 's/.*src="(\/assets\/[^" ]+\.js)".*/\1/p' | head -n 1)"
|
|
remote_mobile_asset="$(curl -fsS --max-time 15 "$remote/h5/" | sed -nE 's/.*src="(\/h5\/assets\/[^" ]+\.js)".*/\1/p' | head -n 1)"
|
|
[[ "$remote_frontend_asset" == /assets/*.js ]] || fail "remote frontend entry asset not found"
|
|
[[ "$remote_mobile_asset" == /h5/assets/*.js ]] || fail "remote mobile H5 entry asset not found"
|
|
|
|
remote_frontend_sha256="$(curl -fsS --max-time 15 "$remote$remote_frontend_asset" | sha256_stream)"
|
|
remote_mobile_sha256="$(curl -fsS --max-time 15 "$remote$remote_mobile_asset" | sha256_stream)"
|
|
local_frontend_sha256="$(sha256 "$frontend_asset_path")"
|
|
local_mobile_sha256="$(sha256 "$mobile_asset_path")"
|
|
echo "remote_frontend_asset=$remote_frontend_asset"
|
|
echo "remote_frontend_asset_sha256=$remote_frontend_sha256"
|
|
echo "remote_mobile_asset=$remote_mobile_asset"
|
|
echo "remote_mobile_asset_sha256=$remote_mobile_sha256"
|
|
[[ "$remote_frontend_sha256" == "$local_frontend_sha256" ]] || fail "remote frontend asset does not match local build"
|
|
[[ "$remote_mobile_sha256" == "$local_mobile_sha256" ]] || fail "remote mobile H5 asset does not match local build"
|
|
echo "remote_asset_match=true"
|
|
fi
|
|
fi
|
|
|
|
echo "release-preflight: read-only checks passed"
|