Telemetria
Il Contributor Portal mantiene la propria telemetria all'interno del progetto
Supabase del portale (xlsjwlmsqonllvtyplml). Il collector OTel usato da
playroll-cpp e playroll-ui non è utilizzato qui. Tenere la
telemetria dentro Postgres significa che una domanda di debug come "questo
utente ha ricevuto l'email di verifica?" è una singola SQL join con
auth.users, auth.audit_log_entries e le tabelle di dominio
(applications, onboarding_submissions, payout_receiving_methods,
contributor_app_links).
Da dove arrivano gli eventi
Due superfici emettono eventi:
- Browser (
surface = 'web') —src/lib/telemetry.tsaccumula gli eventi in batch e li invia via POST ogni 5 secondi (o tramitenavigator.sendBeaconin fase di unload) alla edge functioningest-telemetry. Unsession_idviene generato per page-load e condiviso da tutti gli eventi di quel tab. - Edge function (
surface = 'edge') —activate-contributor-app-linkscrive osservazioni lato server dello stesso flusso di attivazione.
L'endpoint del browser è volutamente invocabile senza JWT (la landing del Contributor Portal è pubblica). Per compensare, la edge function applica un'allowlist di origin, un rate limit per IP (60/min, 300/ora), una quota per sessione (100 eventi), una allowlist stretta sui nomi degli eventi e uno stripper degli attributi PII.
Catalogo eventi
| Evento | Surface | Emesso quando |
|---|---|---|
auth.otp.requested | web | Prima di chiamare signInWithOtp |
auth.otp.sent | web | signInWithOtp ha restituito successo |
auth.otp.failed | web | signInWithOtp ha restituito un errore |
app_link.activate.requested | web | Il browser ha invocato la edge function |
app_link.activate.received | edge | La edge function è entrata nell'handler |
app_link.activate.pending | web/edge | Ha restituito pending_activation |
app_link.activate.verified | web/edge | Ha restituito verified |
app_link.activate.failed | web/edge | La edge function ha catturato un errore |
payout.receiving_method.submitted | web | L'utente ha cliccato salva sul form IBAN |
payout.receiving_method.rejected | web | La validazione client-side è fallita |
payout.receiving_method.persisted | web | INSERT andato a buon fine |
payout.receiving_method.confirmed | web | Conferma email andata a buon fine |
payout.receiving_method.confirm_failed | web | Errore nella conferma email |
ui.unhandled_error | web | Il listener window.error è scattato |
ui.unhandled_rejection | web | Il listener window.unhandledrejection è scattato |
ui.error_boundary | web | Un error boundary React ha catturato un crash |
Gli eventi interni emessi dall'endpoint di ingest stesso
(telemetry.rate_limited, telemetry.policy.violation,
telemetry.events.dropped) stanno a fianco, utili per individuare abusi.
Gestione PII
Email, IBAN, codice fiscale, data di nascita, indirizzi e nomi completi
non devono mai apparire in chiaro negli attrs. Il frontend passa email
al logger; il backend la trasforma in email_hash tramite SHA-256
deterministico di lower(trim(email)). Lo stesso hash è calcolabile
direttamente in SQL:
select public.telemetry_email_hash('philip.budgen@gmail.com');
Per gli IBAN sono ammessi negli attrs soltanto iban_country (i primi 2
caratteri) e iban_last4.
Query comuni
Questo utente ha innescato di recente qualche azione sul portale?
with hash as (
select public.telemetry_email_hash('user@example.com') as email_hash
)
select occurred_at, event_name, severity, error_code, attrs
from public.telemetry_events, hash
where telemetry_events.email_hash = hash.email_hash
or telemetry_events.auth_user_id = (
select id from auth.users where email = 'user@example.com'
)
order by occurred_at desc
limit 100;
Se il risultato è vuoto, l'utente non ha mai raggiunto codice client
strumentato. Se è presente auth.otp.requested senza auth.otp.sent,
il client ha avuto un errore prima di raggiungere Supabase Auth. Se sono
presenti entrambi ma Supabase Auth non ha confirmation_sent_at, il
problema è a valle (Resend SMTP o inbox del destinatario).
Funnel: chi ha superato l'OTP nelle ultime 24h
select event_name, count(*) as count
from public.telemetry_events
where occurred_at > now() - interval '24 hours'
and event_name like 'auth.otp.%'
group by event_name
order by event_name;
Le attivazioni di app-link dovrebbero avvenire una volta per sessione
select session_id, count(*) as calls
from public.telemetry_events
where event_name = 'app_link.activate.requested'
and occurred_at > now() - interval '24 hours'
group by session_id
having count(*) > 1
order by calls desc;
Le righe con calls > 1 indicano una regressione del fix di dedup.
Retention
public.purge_old_telemetry_events(retention_days integer default 90)
cancella le righe vecchie. Schedulala come cron giornaliero dalla
dashboard Supabase → Database → Cron jobs.