feat(worker): cohort skip-guards — no-op every team-iterating job for is_test_cohort teams (PR-1 follow-up) - #90
Merged
Conversation
… is_test_cohort teams (PR-1 follow-up)
Worker-side follow-up to api #246 (W0): teams.is_test_cohort (migration 067,
already on master) tags durable synthetic test-cohort teams. This makes every
background job that charges / churns / emails / quota-nudges a team SKIP an
is_test_cohort team, so continuous synthetic monitoring never pollutes the real
funnel / billing / email surface.
Plan: docs/sessions/2026-06-04/TEST-ACCOUNTS-AND-NR-SYNTHETICS-PLAN.md §1.6.
Mechanism — two skip surfaces, both UNCONDITIONAL + inert (no real team is
is_test_cohort=true today, so behaviour is unchanged for all real teams; this
is a safety guard, not a feature, and does NOT depend on FLOW_SYNTHETIC_ENABLED):
- SQL scans add `AND NOT [t.]is_test_cohort` (teams-joined) or a NULL-safe
NOT EXISTS subselect (testCohortNotExistsClause, for team_id-carrying scans).
- Per-team Go loops can call isTestCohort/skipIfTestCohort (shared helper).
Guarded (§1.6): quota.go (suspend/unsuspend/redis-eviction scans),
quota_wall_nudge.go, churn_predictor.go, expire_imminent.go, expiry_reminder.go,
billing_reconciler.go (primary + orphan sweep), checkout_reconcile.go,
payment_grace_reminder.go, payment_grace_terminator.go, email.go (weekly_digest).
Deliberately NOT guarded (documented in test_cohort_registry_test.go): expire.go
+ expire_stacks.go (TTL REAPERS — must reap synthetic resources/stacks so they
never leak; no customer email), lifecycle_emails.go (pure render layer, gated by
the guarded trigger jobs upstream).
Tests:
- test_cohort_test.go: sqlmock unit coverage of the helper (flagged /
not-flagged / row-vanished / DB-error fail-safe-for-real-teams). 100% of
test_cohort.go.
- test_cohort_registry_test.go: rule-18 enumeration net —
TestAllTeamIteratingJobs_FilterTestCohort fails if any enumerated job loses
its guard, plus TestNotGuardedJobs_AreStillPresent pins the scope boundary.
- test_cohort_integration_test.go: real-Postgres round-trips proving each
SQL-driven job skips a cohort team while processing a normal team
(quota_wall_nudge, churn_predictor, expiry_reminder, expire_imminent,
weekly_digest, checkout_reconcile, payment_grace_reminder,
billing_reconciler). Gated by SetupTestDB (skips no-DB lanes).
testhelpers: add is_test_cohort + the users/pending_checkouts/
payment_grace_periods subset tables + cohort seed/read helpers. The two-gate
note (ci.yml + deploy.yml) holds: the harness change is idempotent and DB-gated
so both -short, no-DB workflows stay green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mastermanas805
enabled auto-merge (squash)
June 4, 2026 20:10
…#90 coverage) cohort.go is exercised only cross-package by internal/jobs integration tests, so the per-package coverage job reported it 0% (94 missing lines) → 100%-patch gate red. Add an in-package smoke test (DB-gated via SetupTestDB, runs in coverage.yml) bringing all 8 seed/query helpers to 100%, mirroring billing_deletion_smoke_test.go. Recurring testhelpers gotcha (3rd: #87/#89/#90) — see memory. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Worker-side follow-up to api #246 (W0).
teams.is_test_cohort(migration 067, already onmaster) tags durable synthetic test-cohort teams. This PR makes every background job that charges / churns / emails / quota-nudges a team SKIP anis_test_cohortteam, so continuous synthetic monitoring never pollutes the real funnel / billing / email surface.Plan:
docs/sessions/2026-06-04/TEST-ACCOUNTS-AND-NR-SYNTHETICS-PLAN.md §1.6.How — two skip surfaces, both UNCONDITIONAL + inert
No real team is
is_test_cohort=truetoday (migration 067 defaults every rowfalse; only a future seeder flips the synthetic teams). So every guard is a pure no-op for all real teams — a safety guard, not a feature. It deliberately does NOT depend onFLOW_SYNTHETIC_ENABLED(that flag gates the future synthetic RUNNER, not these guards).Shared helper
internal/jobs/test_cohort.go:testCohortNotExistsClause(teamIDExpr)— NULL-safeNOT EXISTSSQL fragment for scans whose driving table carries ateam_idbut does not already jointeams. (NOT EXISTS, notNOT IN, so anonymousteam_id IS NULLrows are never silently dropped.)isTestCohort/skipIfTestCohort— per-team Go guard for loops. Fail-safe-for-real-teams: on a DB error it returns "process normally", so a platform-DB blip can never silently suppress a real customer's charge/email.Jobs guarded (§1.6) — each with its skip mechanism
quota.go(suspend / unsuspend / redis-eviction scans)AND testCohortNotExistsClause("r.team_id")on all 3resourcesscansresource.quota_suspendedemailquota_wall_nudge.goAND NOT is_test_cohorton theteamsscanchurn_predictor.goAND NOT t.is_test_cohorton theteamscandidate scanexpire_imminent.goAND testCohortNotExistsClause("r.team_id")resource.expiry_imminentwarning emailexpiry_reminder.goAND testCohortNotExistsClause("r.team_id")anon.expiry_warning12/6/1h warning emailbilling_reconciler.goAND NOT is_test_cohort(primaryteamssweep) +AND NOT t.is_test_cohort(orphanpending_checkoutssweep)checkout_reconcile.goAND testCohortNotExistsClause("pc.team_id")checkout.abandonedemailpayment_grace_reminder.goAND testCohortNotExistsClause("pgp.team_id")payment.grace_reminderdunning emailpayment_grace_terminator.goAND testCohortNotExistsClause("pgp.team_id")payment.grace_terminatedemailemail.go(weekly_digest)AND NOT t.is_test_cohorton theusers JOIN teamsscandigest.weeklyemailDeliberately NOT touched (scope boundary, pinned by
TestNotGuardedJobs_AreStillPresent)expire.go— TTL reaper, not an emailer. Must reap synthetic anon/free RESOURCES so they never leak (plan: keep TTL-expiry of test-cohort resources intact). Only acts ontier IN ('anonymous','free'); seeded synthetic teams are paid-tier and out of scope anyway.expire_stacks.go— TTL reaper for anonymous stacks (NULLteam_id; synthetic deploys are anon/stacks/newper project memory). Must tear down synthetic stacks so namespaces never leak; no customer email.lifecycle_emails.go— pure Go email render layer (no DB scan / team iteration); gated upstream by the guarded trigger jobs.deployment_expirer.go/deployment_reminder.go— not in the §1.6 enumeration. Deploy expiry is a reaper (keep teardown intact); these are out of the stated scope. Synthetic deploys are anon stacks reaped byexpire_stacks; seeded paid teams don't auto-create TTL deploys. Left for a follow-up if synthetic deploy coverage is added.Tests (failing-then-passing verified)
Removed two guards locally →
TestIntegration_QuotaWallNudge_SkipsTestCohortred (cohort got a nudge) +TestAllTeamIteratingJobs_FilterTestCohortred for both files → restored → green.test_cohort_test.go— sqlmock unit coverage of the helper (flagged / not-flagged / row-vanished / DB-error). 100% oftest_cohort.go.test_cohort_registry_test.go— rule-18 enumeration net:TestAllTeamIteratingJobs_FilterTestCohortreds if any enumerated job loses its guard;TestNotGuardedJobs_AreStillPresentpins the scope boundary.test_cohort_integration_test.go— real-Postgres round-trips proving each SQL-driven job skips a cohort team while processing a normal team:TestIntegration_{QuotaWallNudge,ChurnPredictor,ExpiryReminder,ExpireImminent,WeeklyDigest,CheckoutReconcile,PaymentGraceReminder,BillingReconciler}_SkipsTestCohort. DB-gated (skips no-DB lanes).testhelpers: addedis_test_cohort+users/pending_checkouts/payment_grace_periodssubset tables (idempotent) + cohort seed/read helpers. Two-gate note: the harness change is idempotent + DB-gated, so bothci.yml(-race, no DB) anddeploy.yml(-short, no DB) stay green.Coverage block
Gate
make gateGREEN (build + vet +go test ./... -short -count=1— the exact deploy.yml test step).test_cohort.go100% function coverage. Touched files gofmt'd via$(go env GOROOT)/bin/gofmt.🤖 Generated with Claude Code