Show tracker type in cookie tracking policy

Trackers sharing a display name can differ in type, so the generated
cookie and tracking technologies policy was ambiguous without it. Carry
the tracker type through the banner version snapshot and surface it as a
dedicated column in the policy table.

Stop the snapshot from dropping non-cookie trackers so storage, IndexedDB
and cache technologies appear in the policy and served banner config with
their real type. Duration now reflects the type when no max-age applies:
session storage clears with the tab, the remaining storage technologies
persist. Legacy snapshots predate the field and only ever held cookies,
so GetSnapshot backfills an empty type as COOKIE, keeping the non-null
GraphQL enum and policy output valid without a migration.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-06-02 09:53:01 +02:00
parent 346ca94d38
commit 73854f98cb
10 changed files with 59 additions and 16 deletions

View File

@@ -43,7 +43,7 @@ func TestSnapshotsEqual(t *testing.T) {
Description: "Analytics cookies",
Kind: coredata.CookieCategoryKindNormal,
Cookies: coredata.CookieItems{
{Name: "_ga", MaxAgeSeconds: &maxAge, Description: "Google Analytics"},
{Name: "_ga", TrackerType: coredata.TrackerTypeCookie, MaxAgeSeconds: &maxAge, Description: "Google Analytics"},
},
GCMConsentTypes: []string{"analytics_storage"},
PostHogConsent: false,

View File

@@ -80,14 +80,11 @@ func buildSnapshot(
cookiesByCategory := make(map[gid.GID]coredata.CookieItems)
for _, p := range allPatterns {
if p.TrackerType != coredata.TrackerTypeCookie {
continue
}
cookiesByCategory[p.CookieCategoryID] = append(
cookiesByCategory[p.CookieCategoryID],
coredata.CookieItem{
Name: p.DisplayName,
TrackerType: p.TrackerType,
MaxAgeSeconds: p.MaxAgeSeconds,
Description: p.Description,
},

View File

@@ -126,6 +126,18 @@ func (v *CookieBannerVersion) GetSnapshot() (CookieBannerVersionSnapshot, error)
return snapshot, fmt.Errorf("cannot unmarshal cookie banner version snapshot: %w", err)
}
// Snapshots created before tracker types were captured only ever held
// cookie-type trackers, so their cookie items carry an empty tracker
// type. Backfill them as cookies so downstream consumers (policy
// generation, GraphQL, served banner config) see a valid type.
for i := range snapshot.Categories {
for j := range snapshot.Categories[i].Cookies {
if snapshot.Categories[i].Cookies[j].TrackerType == "" {
snapshot.Categories[i].Cookies[j].TrackerType = TrackerTypeCookie
}
}
}
return snapshot, nil
}

View File

@@ -33,9 +33,10 @@ import (
type (
CookieItem struct {
Name string `json:"name"`
MaxAgeSeconds *int `json:"max_age_seconds"`
Description string `json:"description"`
Name string `json:"name"`
TrackerType TrackerType `json:"tracker_type"`
MaxAgeSeconds *int `json:"max_age_seconds"`
Description string `json:"description"`
}
CookieItems []CookieItem
@@ -80,13 +81,22 @@ var cookieDurationUnits = [...]cookieDurationUnit{
{1, "second", 0},
}
// HumanizedDuration renders the cookie's max-age into a human-readable lifetime
// using the same snapping and composition rules as the banner's
// humanizeDuration helper. A nil or non-positive max-age denotes a session
// cookie that is cleared when the browser closes.
// HumanizedDuration renders the tracker's max-age into a human-readable
// lifetime using the same snapping and composition rules as the banner's
// humanizeDuration helper. A nil or non-positive max-age has no fixed
// expiry, so the lifetime is described by the tracker type: session cookies
// are cleared when the browser closes, session storage is cleared when the
// tab closes, and the remaining storage technologies persist until cleared.
func (c CookieItem) HumanizedDuration() string {
if c.MaxAgeSeconds == nil || *c.MaxAgeSeconds <= 0 {
return "Session"
switch c.TrackerType {
case TrackerTypeSessionStorage:
return "Until the tab is closed"
case TrackerTypeLocalStorage, TrackerTypeIndexedDB, TrackerTypeCacheStorage:
return "Persistent"
default:
return "Session"
}
}
remaining := *c.MaxAgeSeconds

View File

@@ -63,6 +63,26 @@ func (v TrackerType) String() string {
return string(v)
}
// Label returns a human-readable name for the tracker type, suitable for
// display in visitor-facing documents such as the cookie and tracking
// technologies policy.
func (v TrackerType) Label() string {
switch v {
case TrackerTypeCookie:
return "Cookie"
case TrackerTypeLocalStorage:
return "Local storage"
case TrackerTypeSessionStorage:
return "Session storage"
case TrackerTypeIndexedDB:
return "IndexedDB"
case TrackerTypeCacheStorage:
return "Cache storage"
default:
return string(v)
}
}
func (v TrackerType) MarshalText() ([]byte, error) {
return []byte(v.String()), nil
}

View File

@@ -489,6 +489,7 @@ type (
TrackerPolicyTracker struct {
Name string
Type string
Purpose string
Duration string
}

View File

@@ -25,9 +25,9 @@ The tables below describe the categories of trackers we use on the Website, the
{{ with .Description }}{{ . }}
{{ else }}Trackers in this category support the functionality described by its name.
{{ end }}{{ if .Trackers }}
| Tracker | Purpose | Duration |
| --- | --- | --- |
{{ range .Trackers }}| {{ .Name }} | {{ .Purpose }} | {{ .Duration }} |
| Tracker | Type | Purpose | Duration |
| --- | --- | --- | --- |
{{ range .Trackers }}| {{ .Name }} | {{ .Type }} | {{ .Purpose }} | {{ .Duration }} |
{{ end }}{{ else }}
We are not currently using any trackers in this category.
{{ end }}{{ end }}

View File

@@ -191,6 +191,7 @@ func (s *GeneratedDocumentService) buildTrackerPolicyDocumentData(
for _, cookie := range c.Cookies {
trackers = append(trackers, docgen.TrackerPolicyTracker{
Name: sanitizeTrackerCell(cookie.Name),
Type: cookie.TrackerType.Label(),
Purpose: trackerPurpose(cookie.Description),
Duration: cookie.HumanizedDuration(),
})

View File

@@ -416,6 +416,7 @@ func (r *cookieBannerVersionResolver) Categories(ctx context.Context, obj *types
for j, c := range cat.Cookies {
cookies[j] = &types.CookieBannerVersionCookie{
Name: c.Name,
TrackerType: c.TrackerType,
MaxAgeSeconds: c.MaxAgeSeconds,
Description: c.Description,
}

View File

@@ -543,6 +543,7 @@ type CookieBannerVersionCategory {
type CookieBannerVersionCookie {
name: String!
trackerType: TrackerType!
maxAgeSeconds: Int
description: String!
}