Consent withdrawal and qualified registration audit
Root causes and implementation
The shared consent cookie changed across origins, while mounted providers retained their initial grant. Observe the cookie on focus, pageshow, visibility, capture-phase interaction, and a one-second timer. Independently re-read consent before enqueueing each application event, update Google consent and clear only the current environment's GA cookies on denial. Docs also checks before CTA events. The timer is a fallback, not a promise of immediate execution in a browser-frozen background tab. Events already transmitted cannot be recalled.
The raw sign_up event occurs before password-email verification. The backend companion preserves that fact and adds qualified_sign_up plus durable verification/qualification fields on users. A qualifying registration must be a new tracked self-service signup, have a verified email, be active and non-staff, and own an active non-internal signup account. Invited members are excluded from the new-customer metric. This version uses existing internal/suspended flags; it is not a comprehensive fraud detector.
Google requires a successful profile response, subject and email_verified=true; GitHub always uses the verified primary address from the authenticated email endpoint, even when the public profile has an email. Official contracts: Google OIDC, GitHub emails.
Verification, qualification and the outbox insertion share the business transaction. A user row lock and the existing unique event key prevent duplicates. Historical users are not backfilled from login. Consent and canonical attribution determine delivery independently of business validity; the sender rechecks internal/suspended eligibility before sending a qualified event. No event is retroactively sent because consent changes.
DEV verification (2026-09-08)
- Actual candidate providers/helpers were compiled into a temporary path on
dev.ecohash.com,console.dev.ecohash.comanddocs.dev.ecohash.com:/__ads_preflight_20260908/. The fixture adapts only Next's pathname hook and adds controls/counters; the changed consent, attribution and event modules run unchanged with the DEV GTM container. - At 15:12:26 UTC each surface had one page view and one observable collect resource. Console withdrawal propagated to the already-open Website/Docs. Further emit actions left both counts at one. Regrant produced a second event/request on each surface. Website withdrawal then propagated back to Console/Docs; further emit actions left counts at two. DEV GA cookies disappeared; the existing production consent cookie remained.
- Browser evidence: visible counter snapshots. Resource Timing counts are not a complete packet capture and do not prove GA4 ingestion. Chrome company profile was used; native incognito control remained unavailable. This is a deployed component regression fixture, not a deployment of all full applications.
- Website: 18 Node tests passed. Docs: 16 passed. Console: 18 passed. TypeScript no-emit checks passed for all three projects.
- Backend: 29 tests passed, no failures/skips, on the DEV bastion with a new localhost-only MariaDB scratch database. Coverage includes actual verification handler, duplicate/concurrent verification, rollback, exclusions, trusted OAuth and historical login, consented/unconsented delivery, and sender eligibility. Provider HTTP responses are mocked; no real OAuth account or payment was created.
Rollout and measurement
Companion changes are required in ecolink, ecolink-web, and ecolink-docs. For the API, apply the additive User schema using the updated worker migrator before routing authentication to new API code. Migration 125 documents the same change: do not run that ALTER after AutoMigrate has already added the columns. The old application can ignore the new nullable columns on rollback; retain them and the event history.
Query users.qualified_signup_at for qualification cohorts and signup_exclusion_reason for exclusions; query the outbox event separately for consented delivery. Retain registration cohorts using users.created_at, since email verification can happen later. Do not treat the qualified count as GA4 received or Ads attributed conversions. Test accounts must be marked internal before qualification, and remain distinguishable in analysis.
Production deployment, global DEV application replacement, GA4 key-event configuration, Ads conversion import and paid traffic were not performed. The temporary fixture has no credentials or registration/payment controls and can be removed independently of existing deployments/routes.