B5 rehearsal fix: sing-box stats registration needs all three lists

The B5 full-stack rehearsal caught a silent accounting killer:
with the production switch set (inbound on, outbound off) the
generated v2ray_api stats block omitted the outbounds list, and
sing-box then registers NO counters at all — QueryStats answered
zero entries while real traffic flowed (A/B against the real core:
adding the list produced full user/inbound/outbound counters).

- builder: whenever the stats block is generated, emit inbounds,
  users and outbounds together; the gates now only decide whether
  the block exists
- master: enforce the inbound category gate at recording time in
  the agent traffic report (the whole-network accounting switch
  semantics: off period is never counted); onlines stay ungated
- golden gate test updated to the corrected contract
This commit is contained in:
Muse
2026-10-23 01:57:52 +00:00
parent 4a532e8c5a
commit c610ab88ea
5 changed files with 98 additions and 35 deletions
+24 -9
View File
@@ -684,8 +684,21 @@ func (a *AgentController) reportTraffic(c *gin.Context) {
jsonMsg(c, "Invalid request", err)
return
}
// ITEM6 B5 recording gate: a sing-box core registers counters
// only when every stats family is listed in its config, so the
// generated config can no longer omit a switched-off category
// (see the stats wiring in SingboxService). The whole-network
// accounting switches (settings.trafficStatsSwitches, the
// master switch on the Settings page) are therefore enforced
// here, at recording time: with the inbound category off
// (either direction), inbound totals and per-user traffic are
// accepted but not recorded — the off period is never counted,
// matching the switches' documented no-backfill semantics.
// Online presence is not traffic accounting and stays ungated.
sw := a.singboxService.GetTrafficStatsSwitches()
recordInbound := sw.InboundUplink && sw.InboundDownlink
db := database.GetDB()
if agent.NodeID != nil {
if recordInbound && agent.NodeID != nil {
for _, ib := range req.Inbounds {
if ib.Tag == "" || ib.Up+ib.Down <= 0 {
continue
@@ -699,15 +712,17 @@ func (a *AgentController) reportTraffic(c *gin.Context) {
}
}
var clientTraffics []*xray.ClientTraffic
for _, u := range req.Users {
if u.Email == "" || u.Up+u.Down <= 0 {
continue
if recordInbound {
for _, u := range req.Users {
if u.Email == "" || u.Up+u.Down <= 0 {
continue
}
clientTraffics = append(clientTraffics, &xray.ClientTraffic{
Email: u.Email,
Up: u.Up,
Down: u.Down,
})
}
clientTraffics = append(clientTraffics, &xray.ClientTraffic{
Email: u.Email,
Up: u.Up,
Down: u.Down,
})
}
if len(clientTraffics) > 0 {
if _, _, err := a.inboundService.AddTraffic(nil, clientTraffics); err != nil {