fix(acceptance): 隔离高并发验收用户组

为生产同构验收创建并绑定专属用户组,避免验收账号继承默认并发 5 的限流。\n\n验收脚本通过管理 API 幂等维护用户组,并在数据库侧校验唯一用户、唯一活跃 Key、并发策略和无外部引用。\n\n验证:bash -n;ShellCheck;git diff --check。
This commit is contained in:
2026-07-31 01:49:12 +08:00
parent 2bfeb3e179
commit a2dea335e0
@@ -219,6 +219,147 @@ wait_for_existing_tasks_to_drain() {
return 1 return 1
} }
ensure_acceptance_user_group() {
local groups_response=$temporary_root/user-groups.json
local group_response=$temporary_root/acceptance-user-group.json
local users_response=$temporary_root/users.json
local user_response=$temporary_root/acceptance-user.json
local group_id group_body user_body isolation_state
admin_request GET /api/admin/user-groups '' "$groups_response"
group_id=$(jq -r '
[.items[] | select(.groupKey == "production-acceptance") | .id]
| if length == 1 then .[0] else empty end
' "$groups_response")
group_body=$(jq -cn '{
groupKey:"production-acceptance",
name:"Production Acceptance",
description:"Dedicated production-isomorphic acceptance traffic",
source:"gateway",
priority:1,
rechargeDiscountPolicy:{},
billingDiscountPolicy:{},
rateLimitPolicy:{
rules:[{
metric:"concurrent",
limit:1000,
leaseTtlSeconds:600
}]
},
quotaPolicy:{},
metadata:{purpose:"production_acceptance",isolated:true},
status:"active"
}')
if [[ -z $group_id ]]; then
[[ $(jq '[.items[] | select(.groupKey == "production-acceptance")] | length' \
"$groups_response") == 0 ]] || {
echo 'acceptance user group lookup was ambiguous' >&2
return 1
}
admin_request POST /api/admin/user-groups "$group_body" "$group_response"
group_id=$(jq -r '.id // empty' "$group_response")
else
[[ $group_id =~ ^[0-9a-f-]{36}$ ]] || {
echo 'acceptance user group API returned an invalid group ID' >&2
return 1
}
isolation_state=$(database_query "
SELECT
(SELECT count(*)
FROM gateway_users
WHERE default_user_group_id='$group_id'::uuid
AND id <> '$AI_GATEWAY_ACCEPTANCE_USER_ID'::uuid)
||':'||
(SELECT count(*)
FROM gateway_api_keys
WHERE user_group_id='$group_id'::uuid
AND gateway_user_id <> '$AI_GATEWAY_ACCEPTANCE_USER_ID'::uuid);")
[[ $isolation_state == 0:0 ]] || {
echo 'acceptance user group is referenced by non-acceptance identities' >&2
return 1
}
admin_request PATCH "/api/admin/user-groups/$group_id" \
"$group_body" "$group_response"
fi
[[ $group_id =~ ^[0-9a-f-]{36}$ ]] || {
echo 'acceptance user group API returned an invalid group ID' >&2
return 1
}
admin_request GET /api/admin/users '' "$users_response"
user_body=$(jq -c \
--arg userID "$AI_GATEWAY_ACCEPTANCE_USER_ID" \
--arg groupID "$group_id" '
[.items[] | select(.id == $userID)]
| if length != 1 then empty else .[0] end
| {
userKey:(.userKey // ""),
source:(.source // "gateway"),
externalUserId:(.externalUserId // ""),
username:(.username // ""),
displayName:(.displayName // ""),
email:(.email // ""),
phone:(.phone // ""),
avatarUrl:(.avatarUrl // ""),
gatewayTenantId:(.gatewayTenantId // ""),
tenantId:(.tenantId // ""),
tenantKey:(.tenantKey // ""),
defaultUserGroupId:$groupID,
roles:(.roles // []),
authProfile:(.authProfile // {}),
metadata:(.metadata // {}),
status:(.status // "active")
}
' "$users_response")
[[ -n $user_body && $(jq -r '.username // empty' <<<"$user_body") != '' ]] || {
echo 'dedicated acceptance user was not found through the control API' >&2
return 1
}
admin_request PATCH "/api/admin/users/$AI_GATEWAY_ACCEPTANCE_USER_ID" \
"$user_body" "$user_response"
isolation_state=$(database_query "
SELECT
(SELECT count(*)
FROM gateway_users
WHERE id='$AI_GATEWAY_ACCEPTANCE_USER_ID'::uuid
AND default_user_group_id='$group_id'::uuid)
||':'||
(SELECT count(*)
FROM gateway_api_keys
WHERE id='$AI_GATEWAY_ACCEPTANCE_API_KEY_ID'::uuid
AND gateway_user_id='$AI_GATEWAY_ACCEPTANCE_USER_ID'::uuid
AND COALESCE(user_group_id,'$group_id'::uuid)='$group_id'::uuid
AND status='active'
AND deleted_at IS NULL)
||':'||
(SELECT count(*)
FROM gateway_api_keys
WHERE gateway_user_id='$AI_GATEWAY_ACCEPTANCE_USER_ID'::uuid
AND status='active'
AND deleted_at IS NULL)
||':'||
(SELECT count(*)
FROM gateway_user_groups
WHERE id='$group_id'::uuid
AND status='active'
AND rate_limit_policy @> '{\"rules\":[{\"metric\":\"concurrent\",\"limit\":1000}]}'::jsonb)
||':'||
(SELECT count(*)
FROM gateway_users
WHERE default_user_group_id='$group_id'::uuid
AND id <> '$AI_GATEWAY_ACCEPTANCE_USER_ID'::uuid)
||':'||
(SELECT count(*)
FROM gateway_api_keys
WHERE user_group_id='$group_id'::uuid
AND gateway_user_id <> '$AI_GATEWAY_ACCEPTANCE_USER_ID'::uuid);")
[[ $isolation_state == 1:1:1:1:0:0 ]] || {
echo 'dedicated acceptance identity isolation verification failed' >&2
return 1
}
echo 'acceptance_user_group=PASS concurrent_limit=1000 active_keys=1 isolated=true'
}
select_acceptance_models() { select_acceptance_models() {
if [[ -z $AI_GATEWAY_ACCEPTANCE_GEMINI_MODEL ]]; then if [[ -z $AI_GATEWAY_ACCEPTANCE_GEMINI_MODEL ]]; then
AI_GATEWAY_ACCEPTANCE_GEMINI_MODEL=$(database_query " AI_GATEWAY_ACCEPTANCE_GEMINI_MODEL=$(database_query "
@@ -1069,6 +1210,7 @@ finish_and_promote() {
} }
verify_release_cas verify_release_cas
ensure_acceptance_user_group
select_acceptance_models select_acceptance_models
ensure_acceptance_model_access ensure_acceptance_model_access
deploy_protocol_emulator deploy_protocol_emulator