From a2dea335e03914cef5c8f25ffdf507f19d0e350a Mon Sep 17 00:00:00 2001 From: wangbo Date: Fri, 31 Jul 2026 01:49:12 +0800 Subject: [PATCH] =?UTF-8?q?fix(acceptance):=20=E9=9A=94=E7=A6=BB=E9=AB=98?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E9=AA=8C=E6=94=B6=E7=94=A8=E6=88=B7=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为生产同构验收创建并绑定专属用户组,避免验收账号继承默认并发 5 的限流。\n\n验收脚本通过管理 API 幂等维护用户组,并在数据库侧校验唯一用户、唯一活跃 Key、并发策略和无外部引用。\n\n验证:bash -n;ShellCheck;git diff --check。 --- scripts/cluster/run-production-acceptance.sh | 142 +++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/scripts/cluster/run-production-acceptance.sh b/scripts/cluster/run-production-acceptance.sh index 4590746..995b9e8 100755 --- a/scripts/cluster/run-production-acceptance.sh +++ b/scripts/cluster/run-production-acceptance.sh @@ -219,6 +219,147 @@ wait_for_existing_tasks_to_drain() { 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() { if [[ -z $AI_GATEWAY_ACCEPTANCE_GEMINI_MODEL ]]; then AI_GATEWAY_ACCEPTANCE_GEMINI_MODEL=$(database_query " @@ -1069,6 +1210,7 @@ finish_and_promote() { } verify_release_cas +ensure_acceptance_user_group select_acceptance_models ensure_acceptance_model_access deploy_protocol_emulator