Payment and admin fulfillment shared the public Redeem entry point, so
fulfillment retries were blocked by, and contributed to, the user's
public redeem failure counter, and lookup errors were silently treated as
"code does not exist" on a partial re-run.
- resolveRedeemAction now distinguishes a genuine not-found lookup from
other lookup errors (which now fail the fulfillment instead of being
swallowed and re-created).
- Add validatePaymentRedeemCode: before reusing an existing code during
fulfillment, verify it belongs to the order (code id, balance type,
amount, user, status), so a stale or foreign code can never be
credited against the wrong order/user.
- Split Redeem into a policy-based core: public redemption enforces the
failure counter; payment fulfillment (redeemForPaymentFulfillment) and
admin fulfillment (RedeemForAdminFulfillment) bypass it, while all code
validation and transactional updates remain identical. Admin
fulfillment keeps the normal redeem-level affiliate rebate.
- The admin CreateAndRedeem endpoint now uses RedeemForAdminFulfillment.
Tests: updated resolveRedeemAction cases, new TestValidatePaymentRedeemCode,
code-used-by-another-user rejection, payment/admin rate-limit bypass
coverage, and public-path regression coverage.
The redeem failure counter used INCR followed by Expire(24h) on every
attempt, so each failed attempt refreshed the key TTL and the window
never actually closed for a user who kept retrying within 24h.
- Count attempts in a fixed 10-minute window via a small Lua script
(INCR, then PEXPIRE only when the key is created or has no TTL), which
is atomic and also repairs keys that lost their TTL.
- Move the counter to the redeem:ratelimit:v2: key space so pre-existing
24h counters do not seed the new window.
- Rename the service threshold to redeemMaxFailedAttempts (30) to reflect
that it bounds attempts per window, not per hour.
Public redeem behavior is unchanged apart from the window semantics:
the check fails open when the counter read fails, and a failed
increment never masks the underlying domain error.
The Antigravity native Gemini route (generateContent) expects each
contents entry to carry an explicit "user" role, matching the request
shape used by the established Antigravity account-test flow. Without it
the probe body is rejected with 400 INVALID_ARGUMENT, so Gemini channel
monitors report false failures.
Add the explicit role to the Gemini provider adapter body and cover it
with a regression test that parses the generated request body.
The Antigravity service fingerprints clients by the version embedded in the User-Agent header. The built-in default (1.23.2) had drifted far behind current real client builds, so requests using the default UA started failing OAuth and probe flows.
Bump DefaultUserAgentVersion from 1.23.2 to 2.9.1 (current client build) and update the constant test accordingly. The default remains overridable via ANTIGRAVITY_USER_AGENT_VERSION and the admin setting.
The Grok cross-client model mapping is enabled by default: InitializeDefaultSettings writes "true", and the parser treats a missing/empty value as enabled ("Default true (missing/empty -> enabled)"). However the comment above the defaults and the admin settings UI hint (EN and ZH) both claimed "disabled by default", contradicting the actual behavior.
Align the comment and both UI hints with the real default. No behavior change.
Claim scheduler-acquired slots before media eligibility checks and task-owner
validation. Release abandoned attempts immediately and retain once-only
cleanup on cancellation and forwarding completion.
Select only the resolved video owner, reuse bounded account waiting without
sticky escape, and preserve the task binding and TTL. Keep generation
failover and owner/billing isolation intact.
Regression tests reproduce unreleased account slots on upstream ab99d56e9
and pass with this change. Validation: go test ./..., unit and integration
suites, focused race tests, backend build, and golangci-lint (0 issues).
Cyber-policy detection only inspected response.failed, and the ingress check ran after error-specific early returns. As a result, error-shaped failures and the connection-pool, HTTP-bridge, and WSv2 paths could miss cyber-policy hits.
Centralize event marking and invoke it before retry and failover returns across the WebSocket implementations. Preserve the recorded guard during account failover and defer connection blocking until the logical failover chain completes, avoiding duplicate records while ensuring subsequent turns are blocked.