feat(server): 委派回收并发防重 + 最小门禁闸门

- delegationRevokeJob 改用 FOR UPDATE SKIP LOCKED + UPDATE RETURNING 原子认领
- regression-workflow-actions 新增 RUN_DELEGATION_CONCURRENCY_CHECK 并发校验开关
- README 补充最小门禁与并发语义说明
这个提交包含在:
2026-02-08 11:38:58 +08:00
父节点 4c7018ca4a
当前提交 0cd44a3d6a
共修改 4 个文件,包含 166 行新增和 53 行删除
+75 -6
查看文件
@@ -12,6 +12,7 @@ FINANCE_PASSWORD="${FINANCE_PASSWORD:-FinancePassword1234}"
CEO_IDENTIFIER="${CEO_IDENTIFIER:-ceo@hyinda.com}"
CEO_PASSWORD="${CEO_PASSWORD:-CeoPassword1234}"
RUN_PAY_CONCURRENCY_CHECK="${RUN_PAY_CONCURRENCY_CHECK:-0}"
RUN_DELEGATION_CONCURRENCY_CHECK="${RUN_DELEGATION_CONCURRENCY_CHECK:-0}"
if ! command -v curl >/dev/null 2>&1; then
echo "[ERROR] curl not found" >&2
@@ -864,6 +865,42 @@ const { createPrismaClient } = require('./src/prismaClient');
});
NODE
}
assert_delegation_auto_revoke_audit_count() {
local delegation_id="$1"
local expected_count="$2"
local scenario="$3"
DATABASE_URL="$DATABASE_URL" node - "$delegation_id" "$expected_count" "$scenario" <<'NODE'
const [delegationId, expectedCountRaw, scenario] = process.argv.slice(2);
const expectedCount = Number(expectedCountRaw);
const { createPrismaClient } = require('./src/prismaClient');
(async () => {
const { prisma, pool } = createPrismaClient();
await prisma.$connect();
try {
const count = await prisma.auditLog.count({
where: {
action: 'DELEGATION_AUTO_REVOKED',
target_id: delegationId,
},
});
if (count !== expectedCount) {
console.error(`[ERROR] ${scenario} expected audit count=${expectedCount} but got ${count}`);
process.exit(1);
}
} finally {
await prisma.$disconnect();
await pool.end();
}
})().catch((error) => {
console.error(error);
process.exit(1);
});
NODE
}
echo "[1/23] Health check + login baseline users"
health_status="$(curl -sS -o "$TMP_DIR/health.json" -w "%{http_code}" "$BASE_URL/health")"
assert_status "$health_status" "200" "health check" "$TMP_DIR/health.json"
@@ -1137,13 +1174,45 @@ ceo_approve_expired_status="$(api_json_auth POST "$BASE_URL/api/workflow/tasks/$
assert_status "$ceo_approve_expired_status" "403" "expired delegation should be forbidden" "$TMP_DIR/delegation-expired-ceo-approve.json"
assert_error_code "$TMP_DIR/delegation-expired-ceo-approve.json" "PERM_002" "expired delegation should be forbidden"
revoke_job_output="$(npm run --silent job:revoke-expired-delegations)"
revoke_job_status="$(printf '%s\n' "$revoke_job_output" | tail -n 1)"
if [[ "$revoke_job_status" != revoked=* ]]; then
echo "[ERROR] revoke expired delegations script output invalid: $revoke_job_output" >&2
exit 1
if [[ "$RUN_DELEGATION_CONCURRENCY_CHECK" == "1" ]]; then
echo "[18.1/23] optional delegation revoke concurrency check"
(
npm run --silent job:revoke-expired-delegations > "$TMP_DIR/delegation-revoke-a.out"
) &
revoke_pid_a=$!
(
npm run --silent job:revoke-expired-delegations > "$TMP_DIR/delegation-revoke-b.out"
) &
revoke_pid_b=$!
wait "$revoke_pid_a"
wait "$revoke_pid_b"
revoke_job_output_a="$(cat "$TMP_DIR/delegation-revoke-a.out")"
revoke_job_output_b="$(cat "$TMP_DIR/delegation-revoke-b.out")"
revoke_job_status_a="$(printf '%s\n' "$revoke_job_output_a" | tail -n 1)"
revoke_job_status_b="$(printf '%s\n' "$revoke_job_output_b" | tail -n 1)"
if [[ "$revoke_job_status_a" != revoked=* ]]; then
echo "[ERROR] revoke concurrency output A invalid: $revoke_job_output_a" >&2
exit 1
fi
if [[ "$revoke_job_status_b" != revoked=* ]]; then
echo "[ERROR] revoke concurrency output B invalid: $revoke_job_output_b" >&2
exit 1
fi
assert_delegation_revoked_db "$DELEGATION_EXPIRED_ID" "delegation auto revoke concurrency"
assert_delegation_auto_revoke_audit_count "$DELEGATION_EXPIRED_ID" "1" "delegation auto revoke concurrency"
else
revoke_job_output="$(npm run --silent job:revoke-expired-delegations)"
revoke_job_status="$(printf '%s\n' "$revoke_job_output" | tail -n 1)"
if [[ "$revoke_job_status" != revoked=* ]]; then
echo "[ERROR] revoke expired delegations script output invalid: $revoke_job_output" >&2
exit 1
fi
assert_delegation_revoked_db "$DELEGATION_EXPIRED_ID" "delegation auto revoke"
fi
assert_delegation_revoked_db "$DELEGATION_EXPIRED_ID" "delegation auto revoke"
manager_approve_after_expired_status="$(api_json_auth POST "$BASE_URL/api/workflow/tasks/$TASK_DELEGATION_EXPIRED/approve" "$MANAGER_TOKEN" "$TMP_DIR/delegation-expired-manager-approve.json" '{}')"
assert_status "$manager_approve_after_expired_status" "200" "manager approve after expired delegation" "$TMP_DIR/delegation-expired-manager-approve.json"