修复退款复核与发布安全边界

补齐退款人工复核、刷新恢复、会话隔离和旧冷备导入兼容
强化真实微信退款对账开关、重复环境变量和生产发布门禁
优化公开作品资产授权查询并保持实时撤销语义
统一后台微信渠道、退款追回流水和画布正式资源测试契约
补齐生成与素材侧栏回归测试
This commit is contained in:
2026-07-14 16:31:22 +08:00
parent aa56c117ec
commit 0baa8cf8cc
34 changed files with 2047 additions and 205 deletions
+49
View File
@@ -56,6 +56,8 @@ function main() {
assertDeployRejectsRelativeReleaseRoot();
assertDeployRejectsRelativeCurrentLink();
assertDeployRejectsRelativeApiEnvFile();
assertRealWechatPayRequiresRefundReconciliation();
assertRealWechatPayUsesLastRefundReconciliationAssignment();
assertDeployCleansStagingReleaseOnFailure();
assertDeployRejectsFinalReleaseRaceAndCleansStaging();
assertMissingBackupScriptFails();
@@ -128,6 +130,11 @@ function assertDeployCopiesPingoraDirectReleaseDependencies() {
'[production-api-deploy] 完成:',
'部署成功时必须输出完成信息。',
);
assertIncludes(
readFileSync(fixture.apiEnvFile, 'utf8'),
'WECHAT_PAY_REFUND_RECONCILIATION_ENABLED=true',
'生产部署必须为存量 API env 补齐退款 reconciliation 开关。',
);
const releaseDir = path.join(fixture.releaseRoot, fixture.version);
const currentTarget = readlinkSync(fixture.currentLink);
@@ -354,6 +361,48 @@ function assertDeployCopiesPingoraDirectReleaseDependencies() {
}
}
function assertRealWechatPayRequiresRefundReconciliation() {
const fixture = prepareFixture('wechat-refund-reconciliation-disabled');
writeFileSync(
fixture.apiEnvFile,
`${readFileSync(fixture.apiEnvFile, 'utf8')}WECHAT_PAY_ENABLED=true\nWECHAT_PAY_PROVIDER=real\nWECHAT_PAY_REFUND_RECONCILIATION_ENABLED=false\n`,
'utf8',
);
const result = runDeploy(fixture);
if (result.status === 0) {
failures.push('真实微信支付关闭退款 reconciliation 时部署必须失败。');
}
assertIncludes(
result.stderr,
'真实微信支付必须设置 WECHAT_PAY_REFUND_RECONCILIATION_ENABLED=true',
'退款 reconciliation 门禁失败时必须给出明确错误。',
);
assertMaintenanceCleared(fixture, '退款 reconciliation 门禁失败');
}
function assertRealWechatPayUsesLastRefundReconciliationAssignment() {
const fixture = prepareFixture('wechat-refund-reconciliation-duplicate-disabled-last');
writeFileSync(
fixture.apiEnvFile,
`${readFileSync(fixture.apiEnvFile, 'utf8')}WECHAT_PAY_ENABLED=true\nWECHAT_PAY_PROVIDER=real\nWECHAT_PAY_REFUND_RECONCILIATION_ENABLED=true\nWECHAT_PAY_REFUND_RECONCILIATION_ENABLED=false\n`,
'utf8',
);
const result = runDeploy(fixture);
if (result.status === 0) {
failures.push(
'退款 reconciliation 重复配置时必须按 systemd EnvironmentFile 的最后一次赋值拦截部署。',
);
}
assertIncludes(
result.stderr,
'真实微信支付必须设置 WECHAT_PAY_REFUND_RECONCILIATION_ENABLED=true',
'退款 reconciliation 重复配置的最后一项为 false 时必须给出明确错误。',
);
assertMaintenanceCleared(fixture, '退款 reconciliation 重复配置门禁失败');
}
function assertDeployRestartsActivePingoraWhenArtifactIncluded() {
const fixture = prepareFixture('with-active-pingora-artifact');
addPingoraGatewayArtifact(fixture);
@@ -196,6 +196,16 @@ const checks = [
'GENARRATIVE_SPACETIME_RUNTIME_SERVICE_BOOTSTRAP_SECRET_FILE=/var/lib/genarrative/spacetime/runtime-service-bootstrap-secret.txt',
reason: '生产 API env 示例只能登记 bootstrap secret 的固定 FILE 路径。',
},
{
file: 'deploy/env/api-server.env.example',
includes: 'WECHAT_PAY_REFUND_RECONCILIATION_ENABLED=true',
reason: '真实微信支付 release 必须在生产 env 示例中显式开启退款主动对账。',
},
{
file: 'scripts/deploy/production-api-deploy.sh',
includes: 'validate_real_wechat_pay_refund_reconciliation',
reason: 'API deploy 必须在 current 切换前复核真实微信支付退款 reconciliation。',
},
{
file: 'jenkins/Jenkinsfile.production-stdb-module-publish',
includes: "file(credentialsId: params.MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID.trim(), variable: 'MIGRATION_BOOTSTRAP_SECRET_FILE')",
+20 -2
View File
@@ -149,6 +149,7 @@ path = Path(sys.argv[1])
key = sys.argv[2]
if not path.exists():
raise SystemExit(0)
matched_value = None
for raw_line in path.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or "=" not in line:
@@ -158,8 +159,9 @@ for raw_line in path.read_text(encoding="utf-8").splitlines():
value = value.strip()
if len(value) >= 2 and value[0] == value[-1] and value[0] in ("\"", "'\''"):
value = value[1:-1]
print(value)
raise SystemExit(0)
matched_value = value
if matched_value is not None:
print(matched_value)
'
if [[ -r "${file_path}" ]]; then
@@ -297,6 +299,7 @@ ensure_runtime_env_and_dirs() {
ensure_env_value "${api_env_file}" "GENARRATIVE_WALLET_REFUND_OUTBOX_BATCH_SIZE" "100"
ensure_env_value "${api_env_file}" "GENARRATIVE_WALLET_REFUND_OUTBOX_FLUSH_INTERVAL_MS" "1000"
ensure_env_value "${api_env_file}" "GENARRATIVE_WALLET_REFUND_OUTBOX_MAX_BYTES" "67108864"
ensure_env_value "${api_env_file}" "WECHAT_PAY_REFUND_RECONCILIATION_ENABLED" "true"
ensure_env_value_migrates_old_default "${api_env_file}" "GENARRATIVE_EXTERNAL_GENERATION_WORKER_LEASE_SECONDS" "3600" "600"
ensure_env_value "${api_env_file}" "GENARRATIVE_EXTERNAL_GENERATION_WORKER_JOB_TIMEOUT_SECONDS" "900"
ensure_env_value "${api_env_file}" "GENARRATIVE_EXTERNAL_GENERATION_WORKER_LONG_JOB_TIMEOUT_SECONDS" "1800"
@@ -317,6 +320,20 @@ ensure_runtime_env_and_dirs() {
fi
}
validate_real_wechat_pay_refund_reconciliation() {
local api_env_file="$1"
local pay_enabled pay_provider reconciliation_enabled
pay_enabled="$(read_env_value "${api_env_file}" "WECHAT_PAY_ENABLED" | tr '[:upper:]' '[:lower:]')"
pay_provider="$(read_env_value "${api_env_file}" "WECHAT_PAY_PROVIDER" | tr '[:upper:]' '[:lower:]')"
reconciliation_enabled="$(read_env_value "${api_env_file}" "WECHAT_PAY_REFUND_RECONCILIATION_ENABLED" | tr '[:upper:]' '[:lower:]')"
if [[ "${pay_enabled}" == "true" && "${pay_provider}" == "real" && "${reconciliation_enabled}" != "true" ]]; then
echo "[production-api-deploy] 真实微信支付必须设置 WECHAT_PAY_REFUND_RECONCILIATION_ENABLED=true: ${api_env_file}" >&2
exit 1
fi
echo "[production-api-deploy] 微信退款 reconciliation 配置已复核: enabled=${reconciliation_enabled:-unset}, real_pay=$([[ "${pay_enabled}" == "true" && "${pay_provider}" == "real" ]] && printf true || printf false)"
}
ensure_worker_runtime_env_defaults() {
local worker_env_file="$1"
@@ -1110,6 +1127,7 @@ if [[ -n "${SPACETIME_SERVER_URL}" ]]; then
fi
ensure_runtime_env_and_dirs "${API_ENV_FILE}"
validate_real_wechat_pay_refund_reconciliation "${API_ENV_FILE}"
ensure_worker_runtime_env_defaults "${WORKER_ENV_FILE}"
migrate_legacy_editor_generation_pricing_override "${CURRENT_LINK}"