fix(release): enforce AIHR tenant collation contract
This commit is contained in:
@@ -68,6 +68,9 @@ grep -q '/h5/' "$mobile_index" || fail "mobile H5 index does not contain the /h5
|
||||
if [[ "${RELEASE_VERIFY_REMOTE_BACKEND:-false}" == "true" && "${RELEASE_VERIFY_REMOTE_MATCH:-false}" != "true" ]]; then
|
||||
fail "RELEASE_VERIFY_REMOTE_BACKEND=true requires RELEASE_VERIFY_REMOTE_MATCH=true"
|
||||
fi
|
||||
if [[ "${RELEASE_VERIFY_REMOTE_BACKEND:-false}" == "true" && "${RELEASE_VERIFY_REMOTE_SCHEMA:-false}" != "true" ]]; then
|
||||
fail "RELEASE_VERIFY_REMOTE_BACKEND=true requires RELEASE_VERIFY_REMOTE_SCHEMA=true"
|
||||
fi
|
||||
if [[ "${RELEASE_VERIFY_REMOTE_SCHEMA:-false}" == "true" && "${RELEASE_VERIFY_REMOTE_MATCH:-false}" != "true" ]]; then
|
||||
fail "RELEASE_VERIFY_REMOTE_SCHEMA=true requires RELEASE_VERIFY_REMOTE_MATCH=true"
|
||||
fi
|
||||
@@ -111,12 +114,12 @@ require_remote_schema() {
|
||||
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'
|
||||
local issues
|
||||
issues="$(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
|
||||
SELECT CONCAT('missing_table:', required.table_name) AS issue
|
||||
FROM (
|
||||
SELECT 'aihr_prompt_template' AS table_name
|
||||
UNION ALL SELECT 'aihr_practice_scenario'
|
||||
@@ -138,13 +141,54 @@ FROM (
|
||||
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;
|
||||
UNION ALL
|
||||
SELECT 'missing_column:sys_oss.tenant_id'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'sys_oss'
|
||||
AND column_name = 'tenant_id'
|
||||
)
|
||||
UNION ALL
|
||||
SELECT CONCAT(
|
||||
'tenant_collation:', aihr.table_name, '.', aihr.column_name,
|
||||
'=', COALESCE(aihr.collation_name, 'NULL'),
|
||||
',expected=', core.collation_name
|
||||
)
|
||||
FROM information_schema.columns aihr
|
||||
JOIN information_schema.columns core
|
||||
ON core.table_schema = aihr.table_schema
|
||||
AND core.table_name = 'sys_oss'
|
||||
AND core.column_name = 'tenant_id'
|
||||
WHERE aihr.table_schema = DATABASE()
|
||||
AND LEFT(aihr.table_name, 5) = 'aihr_'
|
||||
AND aihr.column_name = 'tenant_id'
|
||||
AND aihr.collation_name <> core.collation_name
|
||||
ORDER BY issue;
|
||||
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/, $//')"
|
||||
[[ -z "$issues" ]] || fail "remote schema incompatible: $(printf '%s' "$issues" | tr '\n' ', ' | sed 's/, $//')"
|
||||
|
||||
local processing_join_count
|
||||
processing_join_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(*)
|
||||
FROM aihr_knowledge_attach a
|
||||
LEFT JOIN sys_oss o
|
||||
ON o.oss_id = a.oss_id
|
||||
AND o.tenant_id = a.tenant_id;
|
||||
SQL
|
||||
REMOTE
|
||||
)" || fail "remote processing join check failed: $remote_ssh:$remote_db"
|
||||
[[ "$processing_join_count" =~ ^[0-9]+$ ]] || fail "remote processing join count is invalid: $processing_join_count"
|
||||
echo "remote_schema=16/16 $remote_ssh:$remote_db"
|
||||
echo "remote_tenant_collation=aligned"
|
||||
echo "remote_processing_join_count=$processing_join_count"
|
||||
}
|
||||
|
||||
changed_files="$(git status --porcelain)"
|
||||
|
||||
@@ -11,6 +11,17 @@ MYSQL_CONTAINER="wygj-mysql"
|
||||
DB="wygj_aihr_schema_test_$$"
|
||||
LEGACY_DB="wygj_aihr_candidate_legacy_test_$$"
|
||||
|
||||
tenant_contract_violations="$({
|
||||
find "$ROOT_DIR/backend/script/sql" -maxdepth 2 -type f -name 'aihr_*_mysql8.sql' -print0 \
|
||||
| xargs -0 rg -n '^[[:space:]]*`?tenant_id`?[[:space:]]+varchar\(20\)' \
|
||||
| grep -v 'COLLATE utf8mb4_general_ci'
|
||||
} || true)"
|
||||
if [[ -n "$tenant_contract_violations" ]]; then
|
||||
echo "FAIL: AIHR tenant_id DDL must use the framework utf8mb4_general_ci contract:" >&2
|
||||
printf '%s\n' "$tenant_contract_violations" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while IFS= read -r migration; do
|
||||
migration_name="$(basename "$migration")"
|
||||
grep -Fq "backend/script/sql/update/$migration_name" "$ROOT_DIR/docs/BRD_PRODUCTION_MIGRATION_RUNBOOK.md" || {
|
||||
@@ -185,4 +196,30 @@ test "$candidate_interview_tables" = 'aihr_candidate_employee_link,aihr_candidat
|
||||
candidate_interview_columns="$(mysql "$LEGACY_DB" -N -B -e "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='$LEGACY_DB' AND ((table_name = 'aihr_candidate_material' AND column_name IN ('reviewer','reviewed_time')) OR (table_name = 'aihr_interview_result' AND column_name IN ('questions_json','reviewed_score','review_status','reviewer','review_note','reviewed_time')));")"
|
||||
test "$candidate_interview_columns" = '8'
|
||||
|
||||
echo "PASS: AIHR legacy schema migrations are idempotent, including practice evidence, knowledge feedback, candidate interview review, and position/SOP qualification contracts"
|
||||
mysql "$LEGACY_DB" <<'SQL'
|
||||
CREATE TABLE sys_oss (
|
||||
oss_id bigint NOT NULL,
|
||||
tenant_id varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '000000',
|
||||
PRIMARY KEY (oss_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
CREATE TABLE aihr_collation_probe (
|
||||
id bigint NOT NULL,
|
||||
oss_id bigint NOT NULL,
|
||||
tenant_id varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '000000',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
INSERT INTO sys_oss (oss_id, tenant_id) VALUES (1, '000000');
|
||||
INSERT INTO aihr_collation_probe (id, oss_id, tenant_id) VALUES (1, 1, '000000');
|
||||
SQL
|
||||
if mysql "$LEGACY_DB" -N -B -e "SELECT COUNT(*) FROM aihr_collation_probe a JOIN sys_oss o ON o.oss_id=a.oss_id AND o.tenant_id=a.tenant_id;" >/dev/null 2>&1; then
|
||||
echo "FAIL: mixed-collation tenant join unexpectedly succeeded before migration" >&2
|
||||
exit 1
|
||||
fi
|
||||
mysql "$LEGACY_DB" < "$ROOT_DIR/backend/script/sql/update/aihr_20260715_tenant_collation_mysql8.sql"
|
||||
mysql "$LEGACY_DB" < "$ROOT_DIR/backend/script/sql/update/aihr_20260715_tenant_collation_mysql8.sql"
|
||||
mismatched_tenant_columns="$(mysql "$LEGACY_DB" -N -B -e "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='$LEGACY_DB' AND table_name LIKE 'aihr\\_%' AND column_name='tenant_id' AND collation_name <> 'utf8mb4_general_ci';")"
|
||||
test "$mismatched_tenant_columns" = '0'
|
||||
collation_join_count="$(mysql "$LEGACY_DB" -N -B -e "SELECT COUNT(*) FROM aihr_collation_probe a JOIN sys_oss o ON o.oss_id=a.oss_id AND o.tenant_id=a.tenant_id;")"
|
||||
test "$collation_join_count" = '1'
|
||||
|
||||
echo "PASS: AIHR legacy schema migrations are idempotent, including tenant collation compatibility"
|
||||
|
||||
Reference in New Issue
Block a user