feat(eval): 评测结果回写升 JetStream 持久 —— 消灭最后一处 core NATS 回写 (P1)

完成度审计 P1 + 记忆 nats-durability 的既定规矩「计费/需落库的回写一律
JetStream+幂等,别fire-and-forget」。此前 eval 是全仓最后一处 core NATS pub-sub
回写:网关离线/慢消费者时评测结果直接丢——而质量趋势/门控都依赖它。

照抄已升级的 status/usage 范式(同为 dispatcher→gateway→PG 回写):
- contract 加 StreamEval/ConsumerEval;bus 加 EnsureEvalStream + ConsumeEval
  (durable consumer,AckExplicit,落库失败 Nak 重投自愈),PublishEval 改
  js.Publish 同步等 ack。删 core NATS 的 SubscribeEval。
- gateway/dispatcher 两个 wrapper 在 connect 时 EnsureEvalStream;gateway main
  的评测订阅从 SubscribeEval(fire-and-forget)换 ConsumeEval(handler 返 error→
  Nak),接入优雅停机 drain。
- 幂等前提已满足:SaveEval 按 task_id upsert,at-least-once 重投只覆盖不重复。

验证:e2e 去重测试(TestGatewayQueueDedup)升级到 ConsumeEval,50 条两副本合计
处理50次零重复;live 真端到端:提交任务→跑完→评测经新 eval 流落 PG(level=ok)
一次成功;两服务启动 eval 流 ensure 无报错。go build/vet/test 全绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-18 11:52:40 +08:00
parent 3d15cdb493
commit 940330cdb7
6 changed files with 81 additions and 31 deletions
+4 -1
View File
@@ -52,12 +52,15 @@ const (
ConsumerStatus = "gateway-status" // 状态回写持久消费者(队列组:多网关副本每条只落一次)
StreamUsage = "SUNDYNIX_USAGE" // 用量回写流(持久,计费凭据不丢)
ConsumerUsage = "gateway-usage" // 用量回写持久消费者
StreamEval = "SUNDYNIX_EVAL" // 评测结果回写流(持久,网关离线不丢评测——SaveEval 按 task_id upsert 幂等)
ConsumerEval = "gateway-eval" // 评测回写持久消费者
// BucketCheckpoints 是 HITL 持久化中断的 JetStream KV 桶名:存 compose 图 checkpoint
// (键=task_id)与 resume 记录(键=pending:task_id),dispatcher 重启后可据此恢复在途审批。
BucketCheckpoints = "SUNDYNIX_CHECKPOINTS"
// 自动化评测结果回写:dispatcher 评完经此广播,网关订阅落 PG 供 UI 查询。core NATS pub-sub。
// 自动化评测结果回写:dispatcher 评完经此发到持久流,网关消费落 PG 供 UI 查询。
// 已升 JetStream(此前 core NATS:网关离线/慢消费者会丢评测);SaveEval upsert 幂等,重投安全。
SubjectEval = "sundynix.eval.task"
// Token 用量回写:dispatcher 任务收尾经此广播本轮 token 用量,网关订阅累加到用户日预算并供计费。core NATS pub-sub。