3 March 2026

Modelling consent states in the warehouse

Research notes and a laptop

Most warehouses store the latest CMP string on a user key. That is convenient and wrong for history. A grant at 16:04 does not mean the add-to-bag at 16:01 was optional analytics. If you overwrite, Monday’s funnel becomes Tuesday’s morality play.

We teach a row-level snapshot: every analytics event carries consent_state and consent_recorded_at copied from the CMP at fire time. Unknown is stored as unknown, not as null that a BI tool later coalesces into granted. Withdrawals create a new snapshot; they do not vacuum old rows.

Joins to a slowly changing dimension are fine for reporting “people who are granted today”. They are unsafe as the only fact table. Dual grain — event facts plus a current-state table — keeps both jobs honest.

Late banners are the usual bug. The tag fires, then the CMP returns. If you backfill the first hits as granted, you have invented coverage. Prefer a short “pending” state with a time-to-live, then lock the snapshot. Pending should be visible on the exec pack, not swept into “technical issues”.

This pattern is the spine of our method page. It will annoy anyone who wants a single session ID to mean a single legal story. That annoyance is the point.