修复(#3): 抽奖 GrantLedger.Payload 空字符串违反 MySQL JSON 校验

Closes HIF-3 (Stage 1 P0 from QA smoke - third and final of the same class)

F6 (P0): GrantLedger.Payload=\"\" → MySQL error 3140 (Invalid JSON text: The document is empty)
- 修法:方式 A 对称守卫 Payload=\"{}\" (与 PR E UnmetReasons=\"[]\", PR C PrizeSnapshot.Config=\"{}\" 三处守卫模式统一)
- QA 已 sweep 全部 6 个 lottery JSON 列,这是最后一处漏守
- handler 成功 dispatch 后会 UpdateColumn 覆盖真实 payload;Reserve 阶段用 {} 兜底

回归护栏:TestReserve_EmptyPayloadDefaultsToEmptyJSONObject 用 payloadNotEmptyString per-arg matcher
CI 全绿;2 files, +83/-0

架构师复盘:三次同一 pattern 漏检查(F2 audit ctx keys / F4 UnmetReasons / F6 Payload)。Stage 2 起硬性规则:grep -RIn 'type:json' internal/model/ 全量清单逐列 sweep + 每列至少一条 per-arg matcher 单测。感谢 QA 三次挖坑。
This commit is contained in:
2026-07-09 09:27:14 -07:00
committed by GitHub
parent c92495c5b9
commit 117dc0d6a7
2 changed files with 83 additions and 0 deletions
+7
View File
@@ -51,6 +51,13 @@ func (s *ledgerService) Reserve(ctx context.Context, tx *gorm.DB, entry GrantLed
if entry.ExternalRef == "" {
return nil, false, errors.New("Reserve requires a non-empty ExternalRef")
}
// Payload 是 JSON 列,MySQL 拒绝空字符串(error 3140)——
// handler 在成功 dispatch 后会 UpdateColumn 覆盖真实 payloadReserve 阶
// 段的空 payload 用 "{}" 兜底,与 PrizeSnapshot.Config、
// EligibilitySnapshot.UnmetReasons 的守卫对称。
if entry.Payload == "" {
entry.Payload = "{}"
}
insertRes := tx.WithContext(ctx).
Clauses(clause.OnConflict{DoNothing: true}).