Consent Docs

Data model

The objects PIEDAP Consent stores, how projects, properties, forms, keys, records and requests connect, and what the database enforces.

Reference12 min readFor anyone looking something up

The objects the product stores, how they point at each other, and which links the database enforces. Key ideas explains the same objects in plain language.

The hierarchy#

Organisation, account and project come from the PIEDAP IAM service. The first level this product owns is the property: one site or app.

Project (from IAM)
├── Shared by every property
│   ├── Purposes            why data is used; reused by forms
│   ├── Policies            privacy notices a form links to, in versions
│   ├── Project settings    one row per project
│   ├── Webhook endpoints   with an outbox and a delivery log
│   ├── Rights requests     by person
│   ├── Breach register
│   ├── Evidence anchors    one per day with sealed events
│   ├── Partner register    what each partner is to you
│   ├── Erased hashes       identifiers of erased people
│   └── Audit log           append-only
├── Personal dashboards     each person's own Overview charts
└── Property                a site or app, recognised by its origins
    ├── Consent forms       what is asked
    │   ├── Versions        frozen when published
    │   └── Form purposes   which purposes the form offers, in order
    ├── Region rules        which form each place sees
    ├── Experiments         two forms of the property compared
    ├── Keys                tied to the property, or to the whole project
    ├── Scans and cookies   what was found on the property
    ├── Consent imports     one per file imported
    ├── Notice campaigns    a notice version sent to a list
    │   ├── Uploads, people and their identifiers
    │   ├── Batches and links
    │   └── Campaign anchors, one per day with answers
    └── Consent records     one per person per form
        ├── Events          every state, sealed in a chain
        ├── Linked identifiers
        ├── Email confirmations
        └── Processor tasks, with their reports

Rows of configuration, records and requests carry the organisation, account and project ids. Three tables differ:

  • the audit log may leave them empty
  • a leaf tombstone carries only the project
  • an idempotency record carries the project and the key

The objects#

Object Table Belongs to What it is
Property property Project A website, mobile app, connected-TV app or server integration, with its origins
Purpose purpose Project One reason for using data: key, category, legal basis, data used, partners
Consent form consent_form Property The draft of a notice: wording, layout, settings, languages
Form version consent_form_version Form A published copy, frozen, with a SHA-256 of its exact definition
Form purpose consent_form_purpose Form and purpose That a form offers a purpose, and in which position
Policy policy_document, policy_document_version Project A privacy notice a form links to, in published versions
Region rule region_rule Property, points at a form Which form a continent, country or subdivision sees, and under which law and consent model
Experiment form_experiment Property A comparison of two forms of the same property
Key sdk_key Project, optionally one property A publishable key, an optional secret key, allowed origins, scopes, and live or test
Consent record consent_record Property, form and form version One person's answer to one form as it stands, with any basis, notice and capture context sent
Consent event consent_event Record One state of a record, with its seal. An export or an import adds an event that holds no state and sits outside the chain
Linked identifier consent_subject_link Record Another identifier the record can be found by: type and keyed hash
Email confirmation email_verification Record A pending or confirmed double opt-in
Rights request subject_request Project A request from a person, joined to records by the keyed hash of the identifier
Evidence anchor evidence_anchor Project The signed Merkle root of one day's sealed events
Leaf tombstone evidence_leaf_tombstone Project The leaf hash of a deleted event, so its day's anchor still verifies
Scan and discovered item scan_run, discovered_item Property A site scan, and each cookie, storage key or host found
Webhook webhook_endpoint, webhook_outbox, webhook_delivery Project An endpoint, events waiting to be sent, and the delivery log
Daily statistics consent_daily_stat, consent_impression_daily Form Counts per day, form, country and environment, without identifiers. Banner views are also counted per form version
Project settings project_setting Project Contact, deadlines, retention and similar settings
Breach breach_incident Project One entry of the breach register
Audit log audit_log Project Actions by your team and by secret keys. Append-only: a trigger refuses changes and deletions
Consent import consent_import Property, form and form version One file imported from Records: its browser-reported SHA-256, mapping, stated basis and notice, and counts
Notice campaign notice_campaign, notice_campaign_upload Property and form version One published notice version sent to a list, and each file added to it
Campaign person notice_campaign_recipient, notice_campaign_identifier Campaign One person: hashed identifiers, your encrypted reference, language, basis, reported delivery and answer
Batch and link notice_campaign_batch, notice_campaign_link Campaign Up to 100,000 single-use links and their Merkle root. Tokens are stored as HMACs
Campaign anchor notice_campaign_anchor Project The signed root of one day's notice answers and batches
Partner register partner_register Project A partner's role, contact, days to stop and to erase, DPA date and country
Processor task processor_task, processor_task_acknowledgement Record An identifier-free instruction to one processor to stop or erase, and its append-only reports
Erased hash erased_subject_hash Project Every hash an erased record was known by, with the latest erasure. Kept so an import or a campaign never adds the person back
Personal dashboard user_dashboard Project and one signed-in person One person's arrangement of Overview charts: a name, its place in their list, the charts as JSON and a revision number. A display setting, not evidence
Idempotency record idempotency_record Project and key A stored response for a repeated write, kept 24 hours

How they connect#

One object on the left has many on the right, unless the note says otherwise.

From To Notes
Property Consent forms A form belongs to one property for its whole life
Property Region rules A rule points at one form of the same property
Property Keys A key may also belong to the whole project
Property Scans, discovered items, experiments A scan may also belong to no property
Property Consent records The record takes the property of its form
Consent form Versions Publishing adds a version; versions never change
Consent form Form purposes Each links to one purpose of the project
Purpose Form purposes A purpose can be offered by many forms
Policy Consent forms Optional link from a form to a policy
Consent form Region rules One form can be shown in many places; a place shows one form
Form version Consent records A record names the exact version the person saw
Region rule Consent records Optional: the rule that was applied
Consent record Events Numbered from 1; the sealed ones form the chain
Consent record Linked identifiers Up to five can be added in one call
Consent record Email confirmations Deleted with the record
Consent record Processor tasks Deleted with the record
Consent import Consent records Optional: the import that wrote the current answer
Notice campaign Uploads, people, batches Deleted with the campaign
Campaign person Identifiers, links Up to five identifiers
Webhook endpoint Outbox entries and deliveries Deleted with the endpoint

A region rule does not own a form, and a form does not own a region rule. Both belong to the property, and the rule points at one of that property's forms.

From a page view to a sealed record#

  1. The request carries a key. A key tied to a property reaches only that property.
  2. Without such a key, a property named in the request is used. Failing that, the request's origin decides, and the most specific match wins.
  3. The property's region rules are matched against the person's location. The most specific place wins: subdivision, then country, then continent, then the fallback.
  4. The rule names a form, and its published version is served. A running experiment can serve its other form instead.
  5. The answer is written as a consent record against the version the person was shown.
  6. The new state is sealed as an event and chained to the one before.
  7. After the day closes, the event becomes a leaf of that day's anchor.
Rare cases
  • If two properties match an origin equally, none is chosen. A project with one property needs no origins.
  • A request can name a form, and the region rules then do not choose it.
  • A person may already have a record for the form in the same environment that is not erased. That record is updated.

How consent is proven explains the seal and the anchor.

What the database enforces#

Foreign keys#

Every key inside the hierarchy includes the project id, so a row cannot point at another project's data. Keys that must stay on one site also include the property id. Changing a referenced id is always refused.

Child Columns Parent When the parent is deleted
consent_form project, property property Refused
consent_form policy policy_document Link cleared
consent_form_version project, form consent_form Deleted too
consent_form_purpose project, form consent_form Deleted too
consent_form_purpose project, purpose purpose Refused
policy_document_version project, policy policy_document Deleted too
region_rule project, property property Deleted too
region_rule project, property, form consent_form Refused
form_experiment project, property property Refused
form_experiment project, property, control form and variant form consent_form Refused
sdk_key project, property (may be empty) property Refused
scan_run project, property (may be empty) property Deleted too
discovered_item project, property property Deleted too
consent_record project, property property Refused
consent_record project, property, form consent_form Refused
consent_record project, form, form version consent_form_version Refused
consent_record region rule region_rule Link cleared
consent_event project, consent id consent_record Deleted too
consent_subject_link project, record consent_record Deleted too
email_verification project, consent id consent_record Deleted too
consent_daily_stat project, form consent_form Deleted too
consent_impression_daily project, form consent_form Deleted too
webhook_outbox project, endpoint webhook_endpoint Deleted too
webhook_delivery project, endpoint webhook_endpoint Deleted too
consent_record project, import consent_import Refused
consent_import project, property; property, form; form, form version property, consent_form, consent_form_version Deleted too
notice_campaign project, property, form; form, form version consent_form, consent_form_version Refused
notice_campaign_upload, notice_campaign_recipient, notice_campaign_batch project, campaign notice_campaign Deleted too
notice_campaign_identifier project, person notice_campaign_recipient Deleted too
notice_campaign_link project, batch; project, person notice_campaign_batch, notice_campaign_recipient Deleted too
processor_task project, consent id consent_record Deleted too
processor_task_acknowledgement project, task processor_task Deleted too

Two links use a single column: a form's policy and a record's region rule. Deleting the parent clears the link, and a key that included the project would clear the project id with it. The application checks the project when it writes them.

Uniqueness#

Table Unique on Effect
property project, key Property keys do not repeat in a project
purpose project, key Purpose keys do not repeat in a project
policy_document project, key Policy keys do not repeat in a project
consent_form project, property, key Two properties may each have a form called cookie-banner
consent_form_version form, version Version numbers do not repeat
consent_form_purpose form, purpose A purpose is offered once per form
sdk_key publishable key No two keys share a publishable key
consent_record project, consent id Consent ids do not repeat in a project
consent_event project, consent id, sequence Two events cannot claim the same place in a chain
consent_subject_link record, type, hash An identifier is linked to a record once
evidence_anchor project, day One anchor per project per day
discovered_item project, property, kind, name Each property has its own cookie list
project_setting project One settings row per project
user_dashboard project, person, name A person's dashboard names do not repeat in a project
consent_import project, while writing One import writes at a time in a project
notice_campaign_recipient campaign, hash A person is in a campaign once
notice_campaign_link token hash Link tokens do not repeat
notice_campaign_anchor project, day One campaign anchor per project per day
partner_register project, partner key A partner is registered once
processor_task project, record, partner, action, causing seal One task per processor for each change
erased_subject_hash project, environment, hash, hash version Each erased hash once, with its latest erasure

What follows from these rules#

  • A form, version or property that has consent records cannot be deleted. The evidence would lose the wording it points at.
  • A consent record always names the property of its form and a version of that same form.
  • A region rule can only show a form of its own property.
  • An experiment can only compare two forms of the same property.
  • A form offers only purposes of its own project, and a purpose that a form offers cannot be deleted.
  • Deleting a record removes its events, linked identifiers and email confirmations with it. The retention job keeps a tombstone of each sealed event first.

What the service enforces#

The application checks these rules. No database constraint does.

Rule How it shows
One record per person per form, per environment Concurrent first answers are serialised with a database lock, so two records are not created. An erased record is left as it is; a new answer after erasure starts a new record
A place is claimed by one region rule of a property Saving a second rule for the same country is refused with REGION_ALREADY_CLAIMED. Applying a law preset leaves out the places already claimed and saves the rest. A country rule inside a continent rule is allowed; the more specific wins
A form never moves to another property The update call does not accept a new property
A consent is written against its form's property A request that arrived on one property and names another property's form is refused with FORM_NOT_ON_PROPERTY
Consent can only be recorded against a published form Otherwise CONSENT_FORM_NOT_PUBLISHED
A published version is not edited Changes go to the draft; publishing makes a new version
Events are not updated or deleted while their record exists An erasure writes an ERASED event; it does not remove history
A write with collectedAt never overrules a newer answer, a withdrawal or an erasure It is refused with 409 and one of five codes. See Consent
Every purpose on the version that can be declined must be answered, and nothing else Otherwise INVALID_PURPOSE_DECISIONS or INVALID_PARTNER_DECISIONS
Scheduled jobs that must not run twice run on one replica at a time Each takes a PostgreSQL advisory lock
A person reaches only their own dashboards The person and the project come from the session. Another person's dashboard answers 404
A dashboard is saved over only from the version it was read at A stale revision is refused with 409 and DASHBOARD_CHANGED_ELSEWHERE
Up to 10 dashboards per person in a project, each up to 32,768 bytes Otherwise 409 with DASHBOARD_LIMIT_REACHED, or 400 with DASHBOARD_DEFINITION_TOO_LARGE
Two exceptions
  • The demo data generator sets the time of the events it creates. It is refused in production unless the operator allows it.
  • The vendor-list refresh and the clearing of held-back details on erased records run on every replica. Repeating them changes nothing.

Project settings and form settings#

How a banner behaves is set on each form and published with it. The project keeps what applies to the whole organisation. A published version keeps the settings it was published with.

Setting Where
Privacy button and its position Form
Honour Global Privacy Control Form
Google Consent Mode v2 Form
IAB TCF and Global Privacy Platform Form
Choices per partner Form
Email double opt-in Form, for email opt-in forms
Consent lifetime, languages, wording, layout, purposes Form
IAB CMP id and version Project
Privacy contact, Data Protection Officer, display name Project
Response deadline, evidence retention, inactivity erasure Project
Translation service, subject fingerprint key, what the request form asks for Project
Second person to approve publishing, readable identifier Project

People across records#

There is no "person" table. A person is whatever shares an identifier:

  • A consent record holds the keyed hash of the identifier it was created under.
  • Linked identifiers add more hashes that lead to the same record.
  • A rights request holds the hash of the identifier it was filed under. It can be pointed at a different identifier later.

A lookup hashes the identifier you give and returns every record that matches directly or through a link.

↑↓ to move, Enter to open