fix(devices): pair tokens are unlimited-quota (inherit from User row)

After 67225fd fixed the V2 chat 403 caused by missing SetupContextForToken,
the next probe call surfaced a new 403:
  "token quota is not enough, token remain quota: \$0.000000,
   need quota: \$0.001590"

Root cause: PairDevice initialised the new tokens row with
UnlimitedQuota=false and didn't set RemainQuota, so it defaulted to 0.
Every subsequent V2 chat then failed at pre-consume since the token had
no spendable budget — even though the user's actual User.Quota was
positive.

Device tokens aren't a billing boundary in our model; they're the
Ed25519 binding for a single client install. Quota belongs on the User
row. Flip UnlimitedQuota=true so the relay path consumes from
User.Quota directly, matching exactly what the legacy sk- bearer was
already doing (legacy tokens in this deployment are unlimited too).

Verified end-to-end via /tmp/v2_probe2.js after deploy: POST
/v1/messages with full V2 envelope returns HTTP 200 with the model's
reply.
This commit is contained in:
2026-05-21 03:55:17 +08:00
parent 67225fd67e
commit 16cec0ee9d
+9 -1
View File
@@ -170,7 +170,15 @@ func PairDevice(c *gin.Context) {
CreatedTime: now,
AccessedTime: now,
ExpiredTime: -1, // never naturally; signature validity is the gate
UnlimitedQuota: false,
// Device tokens are an AUTH mechanism, not a per-token billing
// boundary — they pin a device's Ed25519 pubkey to a user row.
// Quota / billing all belong on the User. UnlimitedQuota=true
// means the Manager skips token-level quota gating and consumes
// straight from User.Quota, which is what the legacy sk- bearer
// path also did. Without this, every V2 chat returned 403
// "token quota is not enough" because pair time defaulted
// RemainQuota to 0.
UnlimitedQuota: true,
HideFromUserUI: true, // device tokens only show on Devices page
DeviceId: &deviceIdCopy,
DevicePubkey: &pubkeyCopy,