Перейти к основному содержимому

Status: active

Analytics is split into two lanes that share no identifier and no join key. The separation is the load-bearing design decision in the whole subsystem.

ANONYMOUS LANE                        AUTHENTICATED LANE
analytics.events                      analytics.user_events
analytics.sessions
  visitor_id = HMAC(key, ip:ua)         user_id → auth.user (CASCADE)
  keyed; key is rotatable               stable, identified
  no FK, no user reference              no visitor_id column, ever
  basis: Art 6(1)(f) + LIA              basis: Art 6(1)(b) + 6(1)(f)
  ePrivacy: contested (see below)       ePrivacy: does not engage
  consent tier gates collection         no consent tier — Art 13 disclosure
  retention 60 days                     retention 180 days, or immediate on erasure
  public routes only                    /account/* only
  NOT in collectUserData                IS in collectUserData, section `behavior`

Why the wall exists

If a hashed visitor could be walked to a user id, the anonymous lane would stop being anonymous. Concretely, it would:

  • become reachable by an Art 15 access request and an Art 17 erasure, which it currently is not and is not built to be;
  • invalidate the necessity and balancing analysis in legitimate-interest.md, which is argued on the premise that the lane's subjects are unidentified;
  • turn a short-lived pseudonym into a durable profile, which is precisely the characteristic that separates "counting visits" from "tracking people" in reasonable expectations. The visitor hash is bounded by the 60-day retention window and by a rotatable key, not by daily rotation — daily rotation would make "unique visitors this month" unanswerable, which is the question the lane exists to answer.

Adding a join key between these tables is therefore not a refactor — it is a change of legal position. privacy/report.ts says so in prose; user-events.ts enforces it in schema; the erasure test in privacy.test.ts pins the consequence.

Which lane claims a request

Decided in one place, analytics/collect-policy.ts, so the two collection paths cannot drift:

Path Lane Note
/, /blog/*, /showcases/* anonymous Public surfaces
/account/* authenticated Only when a session exists
/admin/* neither Operator's own usage — high volume, no insight
/desk/* neither Authenticated, excluded by decision
/api/*, /_app/*, *.ico neither Not pages
Bots, prefetch, prerender neither Not visitors

A path is eligible for exactly one lane. Both the server hook and the SPA beacon endpoint import the same predicates — they previously disagreed, and client-side navigations into /admin and /account leaked into the anonymous lane as a result.

The ePrivacy question

ePrivacy Art 5(3) / TDDDG §25 gates access to terminal equipment, independently of whether GDPR is satisfied. Two elements matter:

The session cookie is unambiguous. It writes to the device and is not strictly necessary, so it requires consent. Below the analytics tier it is never set, an existing one is actively deleted, and session grouping falls back to hash(visitorId + UTC day) — the Plausible/Fathom pattern, which stores nothing on the device.

The visitor hash itself is contested, and we do not claim otherwise. No primary source — EDPB, CNIL, DSK, or a court — resolves this exact pattern: no cookie, no added entropy, daily rotation, aggregate-only output. Two credible readings exist:

  • Narrow: IP and User-Agent are unavoidable HTTP transport metadata. Nothing additional is read from or written to the device, so Art 5(3) does not engage and it is purely an Art 6 question.
  • Broad: rooted in Art 29 WP Opinion 9/2014 on device fingerprinting and the technology-neutral framing of "access" in EDPB Guidelines 2/2023 — the User-Agent reports terminal configuration, and combining it with an IP for identification purposes is a form of access however it arrives.

We hold the narrow reading, with the mitigations that make it strongest: no added entropy, daily rotation, aggregate-only output, short retention, and a documented LIA. This is a risk-managed position, not legal certainty. Note also that Germany has no CNIL-style administrative exemption for consent-free audience measurement; the DSK/BfDI consent-management-service regime is a mechanism for capturing consent, not for dispensing with it.

The operative consequence is a rule: never add a signal to the visitor hash. Screen size, timezone, canvas, fonts, hardwareConcurrency — each would convert an arguably-out-of-scope technique into one that is confirmed in scope under Guidelines 2/2023, and would collapse the narrow reading we rely on.

For a logged-in user the ePrivacy gate does not engage: the auth cookie is already strictly necessary under TDDDG §25(2) Nr.2, so reading it requires no further permission. The processing rests on Art 6(1)(b) — operating the account the user asked for — and Art 6(1)(f) for improving it. This is disclosed under Art 13, not consented to under Art 6(1)(a), and the consent banner correctly says nothing about it.

The hard stop is Art 22: nothing derived from this lane may drive a solely automated decision producing legal or similarly significant effects for the user. Aggregate product analytics — cohorts, retention, funnels, which screens get used — is fine. Automated account restriction, materially consequential personalisation, or scoring is not, and would need explicit consent, contractual necessity, or a Member State law basis.

← Back to Blueprint

Думаете, этот паттерн можно сделать лучше? Расскажите как.

Оставить отзыв