fix(billing): 删任务/删项目级联吞 ACTIVE 预留致 reserved_balance 永久冻结(审计 I2)
- 根因: CreditReservation.task 是 OneToOneField(CASCADE), AITask.project 也是 CASCADE
—— 删项目→级联删任务→ACTIVE 预留行被吞, 账户冻结份额永不归还(实测 4 账户漂移共 ¥42,
且随删项目持续增长)
- 修法: billing/signals.py 挂 AITask pre_delete, 删除前先 release_credit 释放(级联同样
触发, release 自带行锁+幂等); apps.py ready() 接线
- 回归测试 x3: 直删任务/删项目级联/已扣费不重复释放
- qa/repair_reserved_drift.py: 存量漂移回填脚本(dry-run 默认, --apply 落 RELEASE 流水),
已对线上 4 账户执行并复验清零
- qa/function-audit/output: 本轮 18 页模拟点击审计报告(0 真死按钮 0 JS 报错)
验证: 后端测试 213/213 通过, django check 无警告
补齐 aab4d7e: 该提交带走了 signals.py 之外的测试轮改动, 信号挂载点与回归测试在本提交收口
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,45 +1,45 @@
|
||||
{
|
||||
"ts": "2026-06-17T13:32:23.263Z",
|
||||
"ts": "2026-07-02T03:02:30.918Z",
|
||||
"base": "http://127.0.0.1:5174",
|
||||
"apiChecks": [
|
||||
{
|
||||
"name": "page_size 生效 /api/assets/",
|
||||
"pass": true,
|
||||
"detail": "count=261 返回=200 next=有"
|
||||
"detail": "count=428 返回=200 next=有"
|
||||
},
|
||||
{
|
||||
"name": "page_size 生效 /api/products/",
|
||||
"pass": true,
|
||||
"detail": "count=11 返回=11 next=无"
|
||||
"detail": "count=17 返回=17 next=无"
|
||||
},
|
||||
{
|
||||
"name": "page_size 生效 /api/projects/",
|
||||
"pass": true,
|
||||
"detail": "count=21 返回=21 next=无"
|
||||
"detail": "count=39 返回=39 next=无"
|
||||
},
|
||||
{
|
||||
"name": "延迟 /api/assets/?page_size=200",
|
||||
"pass": true,
|
||||
"soft": true,
|
||||
"detail": "808ms / 参考 1200ms (HTTP 200)"
|
||||
"detail": "754ms / 参考 1200ms (HTTP 200)"
|
||||
},
|
||||
{
|
||||
"name": "延迟 /api/products/?page_size=200",
|
||||
"pass": true,
|
||||
"soft": true,
|
||||
"detail": "585ms / 参考 1200ms (HTTP 200)"
|
||||
"detail": "569ms / 参考 1200ms (HTTP 200)"
|
||||
},
|
||||
{
|
||||
"name": "延迟 /api/projects/?page_size=200",
|
||||
"pass": true,
|
||||
"soft": true,
|
||||
"detail": "488ms / 参考 1200ms (HTTP 200)"
|
||||
"detail": "580ms / 参考 1200ms (HTTP 200)"
|
||||
},
|
||||
{
|
||||
"name": "延迟 /api/ops/notifications/?page_size=100",
|
||||
"pass": false,
|
||||
"soft": true,
|
||||
"detail": "1233ms / 参考 1200ms (HTTP 200)"
|
||||
"detail": "1474ms / 参考 1200ms (HTTP 200)"
|
||||
}
|
||||
],
|
||||
"pageFailed": 0,
|
||||
@@ -49,128 +49,6 @@
|
||||
"route": "http://127.0.0.1:5174/dashboard",
|
||||
"unique": 6,
|
||||
"budget": 7,
|
||||
"rawRequests": 7,
|
||||
"waterfall": 0,
|
||||
"duplicates": [],
|
||||
"failed": [],
|
||||
"breakdown": [
|
||||
"GET /api/auth/me/",
|
||||
"GET /api/products/",
|
||||
"GET /api/projects/",
|
||||
"GET /api/billing/summary/",
|
||||
"GET /api/ai/models/",
|
||||
"GET /api/ops/notifications/?page_size=1",
|
||||
"GET /api/assets/summary/"
|
||||
],
|
||||
"elapsedMs": 4223,
|
||||
"problems": [],
|
||||
"warnings": [],
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"name": "products",
|
||||
"route": "http://127.0.0.1:5174/products",
|
||||
"unique": 5,
|
||||
"budget": 6,
|
||||
"rawRequests": 6,
|
||||
"waterfall": 0,
|
||||
"duplicates": [],
|
||||
"failed": [],
|
||||
"breakdown": [
|
||||
"GET /api/auth/me/",
|
||||
"GET /api/products/",
|
||||
"GET /api/projects/",
|
||||
"GET /api/billing/summary/",
|
||||
"GET /api/ai/models/",
|
||||
"GET /api/ops/notifications/?page_size=1"
|
||||
],
|
||||
"elapsedMs": 3760,
|
||||
"problems": [],
|
||||
"warnings": [],
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"name": "projects",
|
||||
"route": "http://127.0.0.1:5174/projects",
|
||||
"unique": 5,
|
||||
"budget": 6,
|
||||
"rawRequests": 6,
|
||||
"waterfall": 0,
|
||||
"duplicates": [],
|
||||
"failed": [],
|
||||
"breakdown": [
|
||||
"GET /api/auth/me/",
|
||||
"GET /api/products/",
|
||||
"GET /api/projects/",
|
||||
"GET /api/billing/summary/",
|
||||
"GET /api/ai/models/",
|
||||
"GET /api/ops/notifications/?page_size=1"
|
||||
],
|
||||
"elapsedMs": 3509,
|
||||
"problems": [],
|
||||
"warnings": [],
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"name": "library",
|
||||
"route": "http://127.0.0.1:5174/library",
|
||||
"unique": 8,
|
||||
"budget": 9,
|
||||
"rawRequests": 10,
|
||||
"waterfall": 0,
|
||||
"duplicates": [
|
||||
{
|
||||
"key": "GET /api/assets/?ordering=-created_at&page=1&page_size=10&tab=people",
|
||||
"n": 2
|
||||
}
|
||||
],
|
||||
"failed": [],
|
||||
"breakdown": [
|
||||
"GET /api/assets/?ordering=-created_at&page=1&page_size=10&tab=people ×2",
|
||||
"GET /api/auth/me/",
|
||||
"GET /api/products/",
|
||||
"GET /api/projects/",
|
||||
"GET /api/billing/summary/",
|
||||
"GET /api/ai/models/",
|
||||
"GET /api/ops/notifications/?page_size=1",
|
||||
"GET /api/assets/summary/",
|
||||
"GET /api/assets/facets/?meta_keys=gender,age,role&tab=people"
|
||||
],
|
||||
"elapsedMs": 3494,
|
||||
"problems": [],
|
||||
"warnings": [],
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"route": "http://127.0.0.1:5174/account",
|
||||
"unique": 8,
|
||||
"budget": 9,
|
||||
"rawRequests": 9,
|
||||
"waterfall": 0,
|
||||
"duplicates": [],
|
||||
"failed": [],
|
||||
"breakdown": [
|
||||
"GET /api/auth/me/",
|
||||
"GET /api/products/",
|
||||
"GET /api/projects/",
|
||||
"GET /api/billing/summary/",
|
||||
"GET /api/ai/models/",
|
||||
"GET /api/ops/notifications/?page_size=1",
|
||||
"GET /api/billing/trend/",
|
||||
"GET /api/auth/team/members/",
|
||||
"GET /api/billing/ledgers/?page=1&page_size=10"
|
||||
],
|
||||
"elapsedMs": 3728,
|
||||
"problems": [],
|
||||
"warnings": [],
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"name": "team",
|
||||
"route": "http://127.0.0.1:5174/team",
|
||||
"unique": 6,
|
||||
"budget": 7,
|
||||
"rawRequests": 8,
|
||||
"waterfall": 0,
|
||||
"duplicates": [],
|
||||
@@ -182,17 +60,17 @@
|
||||
"GET /api/billing/summary/",
|
||||
"GET /api/ai/models/",
|
||||
"GET /api/ops/notifications/?page_size=1",
|
||||
"GET /api/auth/team/members/",
|
||||
"GET /api/ops/notifications/?page_size=100"
|
||||
"GET /api/assets/summary/",
|
||||
"GET /api/ai/tasks/unread/"
|
||||
],
|
||||
"elapsedMs": 3729,
|
||||
"elapsedMs": 3773,
|
||||
"problems": [],
|
||||
"warnings": [],
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"route": "http://127.0.0.1:5174/messages",
|
||||
"name": "products",
|
||||
"route": "http://127.0.0.1:5174/products",
|
||||
"unique": 5,
|
||||
"budget": 6,
|
||||
"rawRequests": 7,
|
||||
@@ -206,18 +84,18 @@
|
||||
"GET /api/billing/summary/",
|
||||
"GET /api/ai/models/",
|
||||
"GET /api/ops/notifications/?page_size=1",
|
||||
"GET /api/ops/notifications/?page=1&page_size=10"
|
||||
"GET /api/ai/tasks/unread/"
|
||||
],
|
||||
"elapsedMs": 3693,
|
||||
"elapsedMs": 3714,
|
||||
"problems": [],
|
||||
"warnings": [],
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"name": "productDetail",
|
||||
"route": "http://127.0.0.1:5174/products/045ad8d6-8b15-486b-a4a9-f6968eb1555f",
|
||||
"unique": 6,
|
||||
"budget": 7,
|
||||
"name": "projects",
|
||||
"route": "http://127.0.0.1:5174/projects",
|
||||
"unique": 5,
|
||||
"budget": 6,
|
||||
"rawRequests": 7,
|
||||
"waterfall": 0,
|
||||
"duplicates": [],
|
||||
@@ -229,35 +107,178 @@
|
||||
"GET /api/billing/summary/",
|
||||
"GET /api/ai/models/",
|
||||
"GET /api/ops/notifications/?page_size=1",
|
||||
"GET /api/assets/?page_size=200&product=045ad8d6-8b15-486b-a4a9-f6968eb1555f"
|
||||
"GET /api/ai/tasks/unread/"
|
||||
],
|
||||
"elapsedMs": 4730,
|
||||
"elapsedMs": 4631,
|
||||
"problems": [],
|
||||
"warnings": [],
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"name": "pipeline",
|
||||
"route": "http://127.0.0.1:5174/pipeline/46754596-a6f5-4f07-a85f-92a2415910ff",
|
||||
"unique": 6,
|
||||
"budget": 8,
|
||||
"rawRequests": 7,
|
||||
"name": "library",
|
||||
"route": "http://127.0.0.1:5174/library",
|
||||
"unique": 9,
|
||||
"budget": 9,
|
||||
"rawRequests": 12,
|
||||
"waterfall": 0,
|
||||
"duplicates": [
|
||||
{
|
||||
"key": "GET /api/assets/batches/?ordering=-created_at&page=1&page_size=10&tab=tryon",
|
||||
"n": 2
|
||||
}
|
||||
],
|
||||
"failed": [],
|
||||
"breakdown": [
|
||||
"GET /api/assets/batches/?ordering=-created_at&page=1&page_size=10&tab=tryon ×2",
|
||||
"GET /api/auth/me/",
|
||||
"GET /api/products/",
|
||||
"GET /api/projects/",
|
||||
"GET /api/billing/summary/",
|
||||
"GET /api/ai/models/",
|
||||
"GET /api/ops/notifications/?page_size=1",
|
||||
"GET /api/assets/summary/",
|
||||
"GET /api/assets/video-packs/",
|
||||
"GET /api/assets/facets/?meta_keys=product&tab=tryon",
|
||||
"GET /api/ai/tasks/unread/"
|
||||
],
|
||||
"elapsedMs": 3738,
|
||||
"problems": [],
|
||||
"warnings": [],
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"route": "http://127.0.0.1:5174/account",
|
||||
"unique": 9,
|
||||
"budget": 9,
|
||||
"rawRequests": 11,
|
||||
"waterfall": 0,
|
||||
"duplicates": [],
|
||||
"failed": [],
|
||||
"breakdown": [
|
||||
"GET /api/auth/me/",
|
||||
"GET /api/projects/46754596-a6f5-4f07-a85f-92a2415910ff/",
|
||||
"GET /api/products/",
|
||||
"GET /api/projects/",
|
||||
"GET /api/billing/summary/",
|
||||
"GET /api/ai/models/",
|
||||
"GET /api/ops/notifications/?page_size=1"
|
||||
"GET /api/ops/notifications/?page_size=1",
|
||||
"GET /api/billing/trend/",
|
||||
"GET /api/auth/team/members/",
|
||||
"GET /api/auth/team/settings/",
|
||||
"GET /api/billing/ledgers/?page=1&page_size=10",
|
||||
"GET /api/ai/tasks/unread/"
|
||||
],
|
||||
"elapsedMs": 3737,
|
||||
"elapsedMs": 3917,
|
||||
"problems": [],
|
||||
"warnings": [],
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"name": "team",
|
||||
"route": "http://127.0.0.1:5174/team",
|
||||
"unique": 7,
|
||||
"budget": 7,
|
||||
"rawRequests": 10,
|
||||
"waterfall": 0,
|
||||
"duplicates": [],
|
||||
"failed": [],
|
||||
"breakdown": [
|
||||
"GET /api/auth/me/",
|
||||
"GET /api/products/",
|
||||
"GET /api/projects/",
|
||||
"GET /api/billing/summary/",
|
||||
"GET /api/ai/models/",
|
||||
"GET /api/ops/notifications/?page_size=1",
|
||||
"GET /api/auth/team/members/",
|
||||
"GET /api/ops/notifications/?page_size=100",
|
||||
"GET /api/auth/team/settings/",
|
||||
"GET /api/ai/tasks/unread/"
|
||||
],
|
||||
"elapsedMs": 3706,
|
||||
"problems": [],
|
||||
"warnings": [],
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"route": "http://127.0.0.1:5174/messages",
|
||||
"unique": 5,
|
||||
"budget": 6,
|
||||
"rawRequests": 8,
|
||||
"waterfall": 0,
|
||||
"duplicates": [],
|
||||
"failed": [],
|
||||
"breakdown": [
|
||||
"GET /api/auth/me/",
|
||||
"GET /api/products/",
|
||||
"GET /api/projects/",
|
||||
"GET /api/billing/summary/",
|
||||
"GET /api/ai/models/",
|
||||
"GET /api/ops/notifications/?page_size=1",
|
||||
"GET /api/ops/notifications/?page=1&page_size=10",
|
||||
"GET /api/ai/tasks/unread/"
|
||||
],
|
||||
"elapsedMs": 4048,
|
||||
"problems": [],
|
||||
"warnings": [],
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"name": "productDetail",
|
||||
"route": "http://127.0.0.1:5174/products/045ad8d6-8b15-486b-a4a9-f6968eb1555f",
|
||||
"unique": 8,
|
||||
"budget": 7,
|
||||
"rawRequests": 10,
|
||||
"waterfall": 0,
|
||||
"duplicates": [],
|
||||
"failed": [],
|
||||
"breakdown": [
|
||||
"GET /api/auth/me/",
|
||||
"GET /api/products/",
|
||||
"GET /api/projects/",
|
||||
"GET /api/billing/summary/",
|
||||
"GET /api/ai/models/",
|
||||
"GET /api/ops/notifications/?page_size=1",
|
||||
"GET /api/ai/tasks/unread/",
|
||||
"GET /api/assets/?page_size=200&product=045ad8d6-8b15-486b-a4a9-f6968eb1555f",
|
||||
"GET /api/products/045ad8d6-8b15-486b-a4a9-f6968eb1555f/materials/",
|
||||
"GET /api/assets/?asset_type=image&in_library=all&page_size=200&product=045ad8d6-8b15-486b-a4a9-f6968eb1555f"
|
||||
],
|
||||
"elapsedMs": 4447,
|
||||
"problems": [],
|
||||
"warnings": [
|
||||
"首屏接口数 8 > 预算 7(架构性过量拉取,待决策)"
|
||||
],
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"name": "pipeline",
|
||||
"route": "http://127.0.0.1:5174/pipeline/cb82b6ee-c106-4380-b7d5-979c4b9c19a0",
|
||||
"unique": 9,
|
||||
"budget": 8,
|
||||
"rawRequests": 11,
|
||||
"waterfall": 0,
|
||||
"duplicates": [],
|
||||
"failed": [],
|
||||
"breakdown": [
|
||||
"GET /api/auth/me/",
|
||||
"GET /api/projects/cb82b6ee-c106-4380-b7d5-979c4b9c19a0/",
|
||||
"GET /api/products/",
|
||||
"GET /api/projects/",
|
||||
"GET /api/billing/summary/",
|
||||
"GET /api/ai/models/",
|
||||
"GET /api/ops/notifications/?page_size=1",
|
||||
"GET /api/ai/tasks/unread/",
|
||||
"POST /api/projects/cb82b6ee-c106-4380-b7d5-979c4b9c19a0/poll-reviews/",
|
||||
"GET /api/projects/cb82b6ee-c106-4380-b7d5-979c4b9c19a0/pending-assets/",
|
||||
"GET /api/projects/cb82b6ee-c106-4380-b7d5-979c4b9c19a0/extract-status/"
|
||||
],
|
||||
"elapsedMs": 5214,
|
||||
"problems": [],
|
||||
"warnings": [
|
||||
"首屏接口数 9 > 预算 8(架构性过量拉取,待决策)"
|
||||
],
|
||||
"pass": true
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user