Skip to content
FeaturesPricingAffiliateBlogHelpAboutContact
Get StartedSign In
Back to Blog
industry2026-05-2413 min read

My affiliate balance was anonymized — 360 days instead of 12 months, dormancy calendar bug (PR #635 III F2)

Lukas (35) freelance content creator Vienna Neubau, Instagram + YouTube @lukas.foodtech 41k followers German-language restaurant SaaS niche. thMenu affiliate since summer 2023 (~€38-55/month commissions). May 2025 last commission, 3-month Balkans content-research road trip (Belgrade, Sarajevo, Tirana, Sofia, Bucharest). Mid-May 2026 back in Vienna, login attempt. Account "Anonymized Affiliate AT-X-9NF". Balance €0. Statement: 2026-05-12 dormancy_forfeit €43.80 platform revenue. Lukas s last commission 2025-06-19 — 327 days. ToS section 8.3: "12 months no commissions AND balance < $50 USD → forfeit + anonymize." 12 calendar months = 365.25 days average, **38 days early anonymize**. Lukas couldn t open support ticket because Steuer-ID + IBAN anonymized + identity check fail. Twitter: "@thmenu_en 12 months promised, 11 anonymized. Account back please." Social team 2 hours DM + escalate. Engineering wrong theories busted: (1) Manual admin op? Audit log clean, source aff-dormancy cron 03:00 UTC automatic; (2) last_commission_at wrong? Correct. (3) **Right theory**: cron 12-month math broken. cloudflare/src/cron-jobs/affiliate.ts:891 `const cutoff = now - 12 * 30 * 24 * 60 * 60 * 1000;` — 12 * 30 = 360 days. 12 calendar months ≠ 360 days (average calendar month 30.44 days, 12 months avg 365.25 days leap year averaged). Cron anonymizes 5-6 days early. **Why this matters**: (1) ToS contract breach; (2) anonymize + balance forfeit default irreversible; (3) forfeited balance becomes platform revenue (small €43 but "stolen via calendar bug" bad PR potential); (4) cumulative effect — platform-wide audit 23 affiliates last 6 months early anonymized, €890 total forfeit. **PR #635 batch III F2** fix one line + sanity check: `const cutoffDate = new Date(now); cutoffDate.setUTCMonth(cutoffDate.getUTCMonth() - 12); const cutoff = cutoffDate.getTime();` — JS Date API calendar-month arithmetic + handles leap year/DST/month-length variation. Defense-in-depth pre-anonymize sanity check: monthsSinceLastCommission < 12 → [BEACON:dormancy_anonymize_skipped] warn + skip. **Restore process** Lukas: (1) identity verification multi-signal (email + last commission + last login IP + Stripe customer_id); (2) anonymize reversal — since PR #524 EXT-P1 GG anonymize is soft, encryption-vault tombstone table holds for 90 days, retrieved original full_name + iban_encrypted + tax_id_encrypted; (3) balance restore €43.80 via reversed_anonymize_dormancy_forfeit operation; (4) apology + 3-month free Pro tier. 23 affected affiliates batch reversal 5 days, identity verification manual. 22/23 restored, 1 unreachable (90-day tombstone closed, proactive €50 + 6-month Pro offer flagged for future contact). Lukas Twitter follow-up: "thMenu admitted calendar bug + shipped + Pro tier gift" 12k impressions. Pattern: **multi-month time windows (forfeiture, retention, expiry, grace period) using days-based arithmetic (N * 30 * 24 * 60 * 60 * 1000) give wrong answer. Use setUTCMonth() / setUTCFullYear() for calendar-accurate UTC arithmetic. Leap years + DST + month-length variation handled.** Implementation checklist: (1) grep "\* 30 \*\|\* 365 \*" monorepo-wide; (2) replace with setUTCMonth/setUTCFullYear; (3) handle days-in-month variation (Feb 29 leap edge); (4) pre-action sanity check + threshold cross-check; (5) structured BEACON log marker audit trail; (6) ToS-language match audit legal team; (7) quarterly false-positive sweep (anonymized_at - last_commission_at diff list). Yusuf Sivas version: @yusufeats 28k followers 3-month Southeast Asia trip same flow + ToS contract breach acknowledged.

th

thMenu Team

thmenu.com

Found this helpful? Share it.