14 KiB
14 KiB
积分审计与注册赠分策略改造计划(gstack / plan-eng-review)
1. 目标与结论
本计划解决三个问题:
- 用户删除账号后,积分与成本审计数据不能随业务数据一起丢失。
- 同邮箱重复注册时,不应再次拿到注册赠分。
- 积分消耗审计必须记录真实
input_tokens/output_tokens/cost,不能再写占位值。 - LLM 失败/取消时若平台已产生真实成本,该成本不转嫁用户积分,但必须进入审计账本。
结论:采用 双账本 + 资格账本。
- 保留业务账本:
user_points、points_ledger(在线业务能力) - 新增审计账本:
points_audit_ledger(不可变审计) - 新增资格账本:
register_bonus_claims(注册奖励去重) - 注册赠分策略从 DB trigger 移出,改为应用层策略(配置驱动)
2. 系统边界
2.1 业务域(可删除)
user_points:余额视图points_ledger:业务流水messages/sessions:会话与消息
2.2 审计域(不可级联删除)
points_audit_ledger:审计流水,保留用户快照和成本快照(含用户承担/平台承担归属)register_bonus_claims:注册奖励领取资格记录
2.3 策略域(应用层)
register_bonus_points配置项(默认 60)register_bonus_hmac_key配置项(环境变量注入)- 首登赠分是否发放由服务层决定,不写死在数据库 trigger
3. 现状问题(基于当前代码)
-
注册赠分写死在 DB trigger。
当前函数:public.initialize_profile_and_invite_code_on_signup(),历史上出现过 100/60 改动漂移。 -
积分消费审计写占位值。
在backend/src/v1/points/service.py中,consume_successful_run_points写入input_tokens=0、output_tokens=0、cost=0。 -
删号会丢审计线索。
当前业务删除路径会清理业务数据,缺少独立审计账本保留策略。
4. 数据模型(按项目风格精简命名)
说明:不引入陌生“模板字段”,沿用当前 points_ledger 命名风格。
4.1 新表:points_audit_ledger
idUUID PKevent_idVARCHAR(64) UNIQUE NOT NULLuser_id_snapshotUUID NULLuser_email_snapshotTEXT NULLchange_typeVARCHAR(16) NOT NULLbiz_typeVARCHAR(16) NULLbiz_idUUID NULLdirectionSMALLINT NOT NULLamountBIGINT NOT NULLbalance_afterBIGINT NOT NULLbilled_toVARCHAR(16) NOT NULL --user|platformrun_idVARCHAR(128) NULLrequest_idVARCHAR(128) NULLinput_tokensINTEGER NOT NULL DEFAULT 0output_tokensINTEGER NOT NULL DEFAULT 0costNUMERIC(12,6) NOT NULL DEFAULT 0metadataJSONB NOT NULL DEFAULT '{}'created_atTIMESTAMPTZ NOT NULL DEFAULT now()updated_atTIMESTAMPTZ NOT NULL DEFAULT now()
索引建议:
uq_points_audit_ledger_event_idix_points_audit_ledger_user_id_created_at(user_id_snapshot,created_at DESC)ix_points_audit_ledger_change_type_created_at(change_type,created_at DESC)
4.2 新表:register_bonus_claims
idUUID PKemail_hashVARCHAR(64) UNIQUE NOT NULLuser_email_snapshotTEXT NOT NULLfirst_user_idUUID NULLgrant_event_idVARCHAR(64) UNIQUE NOT NULLcreated_atTIMESTAMPTZ NOT NULL DEFAULT now()updated_atTIMESTAMPTZ NOT NULL DEFAULT now()
注:email_hash 由标准化邮箱(trim + lower)计算(HMAC-SHA256,key 来自 register_bonus_hmac_key)。
5. 数据流设计
5.1 注册赠分流程(应用层,非 trigger)
[用户首登/注册完成]
-> PointsPolicyService.load(register_bonus_points)
-> normalize(email) -> email_hash
-> INSERT register_bonus_claims(email_hash, ...)
- 成功: 继续发放积分
- 唯一冲突: 说明历史已领取,跳过发放
-> 更新 user_points
-> 写 points_ledger
-> 写 points_audit_ledger
-> commit
5.2 运行消耗积分流程(写真实成本)
[run completed]
-> 从持久化消息/会话聚合真实 usage
(input_tokens, output_tokens, cost)
-> PointsService.consume_successful_run_points(...)
-> 更新 user_points
-> 写 points_ledger
-> 写 points_audit_ledger(真实 usage)
-> commit
5.3 运行失败/取消但平台发生成本流程(不扣用户,记平台账)
[run failed/canceled]
-> 从持久化消息/事件聚合真实 usage
-> 若 cost > 0:
- 不调用用户扣分
- 写 points_audit_ledger(
direction=0,
amount=0,
billed_to='platform',
input_tokens/output_tokens/cost=真实值,
metadata.reason='run_failed_or_canceled_platform_billed'
)
-> commit
5.4 删除账号流程
[delete account]
-> 删除 user_points / points_ledger / sessions / messages / profile / auth
-> 保留 points_audit_ledger / register_bonus_claims
6. 失败模式与处理
6.1 双写不一致(P0)
- 场景:
points_ledger写成功,points_audit_ledger写失败。 - 策略:同事务写入,任一失败全部回滚。
6.2 并发重复注册(P0)
- 场景:同邮箱并发首登,发放多次。
- 策略:
register_bonus_claims.email_hash唯一约束 + 冲突即跳过。
6.3 邮箱规范化不一致(P1)
- 场景:
User@A.com与user@a.com被当成不同人。 - 策略:统一 normalize(trim + lower)后再 hash。
6.4 成本快照缺失(P1)
- 场景:run 成功但 usage 聚合取不到,写入 0。
- 策略:
- 业务是否扣分与成本写入解耦:允许扣分,但审计需标记
metadata.usage_missing=true - 记录 warning 日志并纳入告警指标
- 业务是否扣分与成本写入解耦:允许扣分,但审计需标记
6.5 失败/取消真实成本归属(P0)
- 场景:LLM 回调失败或用户取消,但上游已计费。
- 策略:
- 不扣用户积分(
user_points、points_ledger不变) - 审计账本强制落一条平台承担记录(
billed_to='platform') - 该记录必须包含真实
input_tokens/output_tokens/cost
- 不扣用户积分(
7. 信任边界与安全
user_email_snapshot必须来自服务端认证上下文,不接受客户端传入。input_tokens/output_tokens/cost必须来自服务端持久化记录,不接受客户端上报。- 审计表只允许后端 service-role 写入,不暴露客户端写接口。
register_bonus_claims不应被普通业务接口更新/删除。register_bonus_hmac_key仅后端可读,不下发客户端,不写日志。
8. 实施步骤(最小改动优先)
Phase 1: 协议与配置
- 更新协议文档:
docs/protocols/common/user-points-chat-data-protocol.md- 新增“审计留存与注册奖励策略”章节
- 新增配置:
register_bonus_points(默认 60)
Phase 2: 数据库迁移
- 新增表:
points_audit_ledger - 新增表:
register_bonus_claims - 不改现有
points_ledger、user_points结构
Phase 3: 服务层改造
- 移除 trigger 中注册送分逻辑(trigger 只保留 profile/invite 初始化)
- 在应用层增加注册奖励发放逻辑(带资格检查)
- 在积分消费路径改造为真实 usage 写审计
- 在失败/取消路径增加平台承担成本审计(不扣用户)
Phase 4: 删除链路校验
- 删除账号后验证业务表清理
- 验证审计表与资格表仍可查
9. 测试覆盖计划
9.0 P0 测试门槛(实现前锁定)
以下测试为上线前阻断项,任一缺失不得合并:
- 幂等回放:同一
event_id重放不重复写points_audit_ledger。 - 注册送分去重:同邮箱(normalize 后)重复注册不重复发放积分。
- 事务一致性:业务账本写入成功但审计写入失败时,整体回滚。
- 删除后重注册:删号后同邮箱重注册仍不再发放首登奖励。
- 失败/取消审计:run 失败与取消场景写审计但不扣积分。
- 成本归属:失败/取消且
cost>0的记录必须为billed_to='platform'。
9.1 单元测试
- 邮箱 normalize/hash 一致性
- 注册奖励配置读取与默认值
- usage 聚合函数(含空值和异常值)
9.2 集成测试
- 首次注册发放奖励成功
- 同邮箱重复注册不再发放
- 并发注册仅一次成功发放
- 消费积分写入真实 tokens/cost 审计
- 失败/取消且平台发生成本时,写平台承担审计且不扣用户积分
- 删号后审计数据保留
9.3 回归测试
- 现有积分余额查询和扣分逻辑不回归
- 邀请码流程不回归
10. 文件级改造清单
数据库 / 模型
backend/alembic/versions/*新增迁移:创建两张新表backend/src/models/points_audit_ledger.py新增backend/src/models/register_bonus_claims.py新增
积分服务与仓储
backend/src/v1/points/repository.py:新增审计写入、资格检查方法backend/src/v1/points/service.py:- 新增注册奖励发放入口(配置驱动)
- 消费路径写真实 usage 审计
- 失败/取消路径写平台承担成本审计
运行时调用链
backend/src/core/agentscope/runtime/tasks.py:- 在扣分点传入真实 usage(或可计算上下文)
- 在 run 异常/取消路径传入 usage 并落平台承担审计
协议文档
docs/protocols/common/user-points-chat-data-protocol.md更新
11. 取舍说明
为什么不直接改 points_ledger 为审计表
- 会把在线业务与审计诉求耦合在一张表,后续权限和迁移风险高。
- 当前最小改动方案是新增审计表,保持业务链路稳定。
为什么保留 event_id
id是技术主键,只保证行唯一。event_id是业务幂等键,防重放、防重试重复记账、支持跨表对账。
12. 未决事项
user_email_snapshot是否明文存储,还是仅内部可解密存储。- 审计数据保留时长(默认建议至少 1 年)。
- 成本单位与精度是否统一沿用
NUMERIC(12,6)。
13. PR 拆分与执行顺序(可直接实现)
PR1:数据库与协议落地(不改业务行为)
目标:先建立新数据边界,不改变线上积分逻辑。
改动范围:
backend/alembic/versions/*:新增迁移,创建points_audit_ledger、register_bonus_claimsbackend/src/models/points_audit_ledger.py:新增模型backend/src/models/register_bonus_claims.py:新增模型docs/protocols/common/user-points-chat-data-protocol.md:补审计与注册送分策略契约
验收标准:
- 迁移可执行、可回滚
- 新表索引与唯一约束生效
- 协议文档与表结构一致
测试要求:
- 迁移 smoke test
- 约束与索引存在性校验
PR2:注册送分策略迁移到应用层(去 trigger 固化)
目标:把注册送分从 DB trigger 移到应用层唯一触发点(注册回调)。
改动范围:
backend/src/v1/points/service.py:新增注册奖励发放入口与资格校验backend/src/v1/points/repository.py:新增register_bonus_claims检查/写入backend/src/core/config/settings.py(或等价配置入口):新增register_bonus_pointsbackend/src/core/config/settings.py(或等价配置入口):新增register_bonus_hmac_key- 相关注册回调调用链文件:接入
grant_register_bonus_if_eligible(...) - 迁移调整:更新 trigger,移除注册送分写入逻辑,仅保留 profile/invite 初始化
验收标准:
- 新注册触发一次赠分
- 同邮箱重复注册不再赠分
- 配置变更可控制赠分值(默认 60)
- 邮箱哈希稳定且不可逆(同邮箱同哈希,不暴露明文)
测试要求:
- 并发注册去重测试(唯一约束 + 冲突路径)
- 删除账号后同邮箱重注册不赠分
- event_id 幂等回放不重复发放
- 缺失
register_bonus_hmac_key时服务启动失败(fail fast)
PR3:真实成本审计与删除链路联调
目标:将 run 真实 usage 写入审计,并覆盖成功/失败/取消三种对话轮次;失败/取消场景发生真实成本时记平台承担。
改动范围:
backend/src/v1/points/service.py:消费路径审计写入(真实 tokens/cost)backend/src/v1/points/repository.py:新增append_audit_ledger(...)backend/src/core/agentscope/runtime/tasks.py:传递该轮次必要上下文- 账号删除服务链路:确认保留
points_audit_ledger/register_bonus_claims
验收标准:
- 成功对话:扣分 + 审计
- 失败/取消对话:不扣分 + 审计(若有成本则
billed_to='platform') - 审计中的
input_tokens/output_tokens/cost为真实值,不再占位 0
测试要求:
- 成功/失败/取消三路径集成测试
- 事务一致性测试(业务写成功 + 审计写失败 -> 回滚)
- 删除后审计保留验证
- 失败/取消 +
cost>0平台承担场景回归测试
PR4:观测与运维保障(建议同迭代完成)
目标:避免审计静默失真。
改动范围:
- 指标与日志:
points_audit_write_failed_totalpoints_usage_missing_total
- 告警阈值:连续失败或短时突增告警
- 运维文档:异常重放与人工核对流程
验收标准:
- 审计写入失败可被监控发现
- usage 缺失可被监控发现并可追溯到事件
14. 实施完成定义(DoD)
满足以下全部条件才算完成:
- 计划中的 P0 测试门槛全部通过。
- 注册赠分不再依赖 DB trigger 写死值。
points_audit_ledger记录真实 usage,覆盖成功/失败/取消。points_audit_ledger记录真实 usage,覆盖成功/失败/取消;失败/取消有真实成本时归属platform。- 删除账号后业务数据清理,审计与资格数据保留。
- 关键失败有指标与告警,不允许静默失败。