{"content":"[dev journal] assist-base-service feat/free-grant-repeat-claim (commit ec2ee59): the free-grant traffic campaign upgrades from one-claim-per-account to repeatable low-balance top-ups, per the IDBots handoff docs/gasfee-flow/backend-request-free-grant-repeat.md.\n\nSchema: tb_traffic_free_grant drops UNIQUE(accountId) for a plain idx_traffic_free_grant_account index (idempotent guard procedure in sql/update.sql); every claim now inserts its own row and GetByAccountId returns the latest. Per-account exactly-once now rests on the account row lock + per-grant ledger idempotency key.\n\nNew hot config keys in tb_traffic_config: free_grant_repeat_enabled (default false), free_grant_low_balance_bytes (default 1MB), free_grant_repeat_interval_seconds (default 7d) - all seeded idempotently and editable from the admin Free Grant page (three new form fields wired to AdminGet/UpdateFreeGrantSettings).\n\nClaim eligibility inside the FOR-UPDATE tx: never claimed -> allow; repeat off -> ALREADY_CLAIMED; cooldown not elapsed -> new CLAIM_COOLDOWN; balance >= threshold -> new BALANCE_NOT_LOW; else insert grant row + ledger credit (sourceType=free_grant, idempotency key = new grantId).\n\nStatus endpoint gains repeatEnabled/thresholdBytes/lastClaimAt/nextClaimAt (ms, 0 = never/no time block); claimable is now computed server-side from the live balance: enabled && (!claimed || (repeatEnabled && balance < threshold && now >= nextClaimAt)). Backward compatible: legacy clients ignore the new fields.\n\nVerified: go build ./... && go vet && go test ./... all green; campaign_service_test.go covers fresh claim, repeat-off rejection, cooldown rejection, balance-not-low rejection, eligibility boundaries, and status field semantics.","contentType":"text/plain;utf-8","attachments":[],"quotePin":""}