Report nightly seat usage to the billing provider

Go · Go · advanced · modification

Moves seat billing from the monthly CSV that finance uploaded by hand to daily metered usage. `errgroup` with `SetLimit(8)` keeps us inside the provider's concurrency limit, every call gets a 10-second timeout, and each org is reported under an idempotency key, so on-call can simply rerun the job after a failure. The summary line lists the failed org ids, and any failure is returned to the scheduler so it pages. Tested against the provider's sandbox with 40 test customers: all 40 reported, and a rerun was deduplicated.

Go 1.22 worker; the module already requires `golang.org/x/sync` (v0.10). `usage.Store` (existing) has `ActiveOrgs(ctx) ([]Org, error)` (`Org` has `ID int64` and `BillingCustomerID string`) and `SeatsUsed(ctx, orgID int64, day time.Time) (int64, error)`; its queries run under the pool's 5-second statement timeout. `deps` in `cmd/worker/main.go` (unchanged) already carries `UsageStore usage.Store` and `Billing *billing.Client`. `billing.Client` (existing) has `ReportUsage(ctx context.Context, customerID string, quantity int64, idempotencyKey string) error` over HTTPS, which returns an error for any non-2xx answer or a cancelled context. `scheduler.Scheduler.Daily(name, at string, fn func(ctx context.Context) error)` runs `fn` once a day at a UTC time, never overlaps two runs of the same job, and pages on-call when `fn` returns an error. About 3,000 active orgs; the provider deduplicates idempotency keys for 30 days. A night the worker was down is backfilled by hand.

Requirements

Files touched

--- cmd/worker/jobs.go
 
 import (
+	"time"
+
 	"example.com/seats/internal/scheduler"
+	"example.com/seats/internal/usage"
 )
 
 func registerJobs(s *scheduler.Scheduler, d deps) {
 	s.Daily("sessions-cleanup", "01:00", d.Sessions.Run)
+
+	seats := &usage.Job{Store: d.UsageStore, Billing: d.Billing, Now: time.Now}
+	s.Daily("seat-usage-report", "02:00", seats.Run)
 }
 
--- internal/usage/job.go
+package usage
+
+import (
+	"context"
+	"fmt"
+	"log/slog"
+	"sync"
+	"time"
+
+	"golang.org/x/sync/errgroup"
+)
+
+const (

Review this PR

Go practice