SOC 2 audit found missing tier_events rows — applyTiersToAll silent failure (PR #629 HHH-prelim)
Cillian Dublin Donnybrook 41-yo 14-yr independent SOC 2 + ISO 27001 audit practice 8-yr Big Four risk advisory + 6-yr solo mid-sized Irish + UK SaaS Type II audits + ISO 27001 surveillance. Q2 2026 thMenu annual SOC 2 Type II re-certification CC7.1 + CC7.2. Methodology sample-based testing audit-log table × system state-change events cross-correlate affiliate tier changes sample. tier_events table 60 rows tracking affiliate_profiles.commission_rate changes past 90 days 72 distinct tier transitions. 12 rows missing SOC 2 CC7.1 logging all material system changes missing audit entries gaps. Cillian compliance team 12 of 72 affiliate tier changes no tier_events entry which process inserting nominal partial-failure. Engineering 3 wrong theories (1) manual super-admin tier flip maybe no audit 0 manual flips 90 days; (2) Stripe webhook async race PR #621 FFF F3 OCC race-guard shipped webhook tier_events normal; (3) PR #621 FFF F3 race-guard skip cases no audit 3 cases not 12. Correct theory applyTiersToAll cron tier_events INSERTs failing bulk return silently swallowing. Forensic cloudflare/src/cron-jobs/affiliate.ts:applyTiersToAll + apps/web-admin/src/lib/affiliate/tier.ts:evaluateTier. PR #626 batch GGG F1 evaluateTier return type audit_logged: boolean tier upgrade succeeded commission_rate updated but tier_events INSERT failed (Supabase outage FK constraint mismatch transient error) detect partial-failure surface. Stripe webhook handler applying pattern const result = await evaluateTier; if (!result.audit_logged) console.warn [BEACON:tier_audit_failed]. SOC 2 logger pickup. But applyTiersToAll discarded flag const results = await Promise.all(affiliates.map(a => evaluateTier(a.id))); return { total: affiliates.length, upgraded: results.filter(r => r.upgraded).length }. Only upgraded count audit_logged: false dropped inside loop aggregate response 100 affiliates processed 12 upgraded green check reality 12 upgraded but 3 tier_events INSERT failures audit gap cron logger not see. Source 12 missing rows transient Supabase API errors nightly applyTiersToAll commission_rate update succeed (atomic OCC race-guard commission_rate update first) then tier_events INSERT Supabase 503 evaluateTier audit_logged: false applyTiersToAll discard. PR #629 batch HHH-prelim minimal fix two new return fields upgraded_audit_failed: number + upgraded_audit_failed_ids: string[] capped 100. Inside loop const auditFails = results.filter(r => r.upgraded && !r.audit_logged); return { total, upgraded, upgraded_audit_failed: auditFails.length, upgraded_audit_failed_ids: auditFails.map(r => r.affiliate_id).slice(0, 100) }. count > 0 console.warn [BEACON:tier_audit_failed_bulk] Logpush + Sentry SOC 2 logger now sees partial-failure. Production backfill 12 missing rows commission_rate + tier_changed_at × tier_events cross-correlate INSERT tier_events source cron:applyTiersToAll:retro-backfill. Cillian MEDIUM severity audit findings deploy + backfill RESOLVED 2026 SOC 2 Type II clean opinion. Cillian LinkedIn 48 hours reproduce + fix + backfill 2.6k. Selami Kayseri Erkilet 16-yr SOC 2 + ISO 27001 parallel Turkish thMenu SOC 2 Type II audit same clean opinion. Pattern per-item function partial-failure flag (audit_logged: false) bulk caller MUST forward aggregate response silent-failure-mode pattern aggregate API all green reality some failed downstream gap never sees. Sibling-surface sweep cron/process-deletions r2_objects_attempted vs r2_objects_deleted + cron/affiliate.releaseClawback clawback_status + cron/feedback-sentiment AI failure ratio + cron/inventory-predict AI + push failure ratio. Implementation per-item function partial-failure flag explicit return type + bulk caller filter + count + aggregate *_audit_failed *_failed_ids capped + structured [BEACON:] log marker + Sentry pickup + ops dashboard partial-failure widget + PR template checkbox + quarterly audit cross-correlate. PR #629 reference.
thMenu Team
thmenu.com
Found this helpful? Share it.
Related articles
Why Digital Menus Increase Restaurant Revenue by Up to 30%
Studies show restaurants using digital QR menus see measurable increases in aver…
When a Customer Downgrades, What Happens to Old Features? — The Silent Feature-Drift Problem in SaaS
Most SaaS apps run a single line of code when a customer downgrades — but old fe…
JWT alg-confusion attack — why Supabase's HS256 → RS256/JWKS migration breaks legacy verifiers
Verifiers that never decode the JWT header are wide open to `alg=none` and alg-c…