Privacy architecture

The boundary is structural, not editorial.

Most privacy claims describe what a company promises not to do with data it holds. This one describes data the platform is incapable of holding, because no field, table, or contract exists to carry it.

01 · The data flow

Four stages. Identity stops at the second one.

Stage 01

Mobile wallet

The patron’s credential lives in their wallet, signed by the issuing authority. It never leaves the phone as a document.

Stage 02

Encrypted local verification

The verifier requests one element over an encrypted session and validates it on the device. This is where identity data begins and ends.

Stage 03

Age result

One of three closed outcomes is rendered to staff. This is the only patron-derived value that exists after the session closes.

Stage 04

Privacy-safe operational aggregate

A counter for a location, an outcome, and a day. Nothing about a person reaches this stage, because nothing about a person left stage two.

02 · What Laurel receives

The complete inbound vocabulary.

This is the entire set of fields a verifier is permitted to send. It is an allowlist: a device sending anything else gets a validation error, not a silently stripped field.

Field Purpose
event ID Random per event; used only for retry de-duplication
device ID Which verifier — a piece of your equipment
location ID Which of your sites
result pass · fail · unable_to_verify
failure reason Closed category, only when unable_to_verify. Never free text
occurred at When the check happened
app version Semantic version of the verifier
configuration version Optional; which configuration the device was running

Read that list again

Every field describes your equipment or your operation. Exactly one field is patron-derived — the result — and it has three possible values.

The failure reason is deliberately coarse

It exists so an operator can tell “the tap did not connect” from “the trust material needs updating.” It never explains why a fail happened, because that explanation would be a fact about a person.

03 · The prohibited list

Never requested. Never received. Never stored.

Not “deleted after 30 days.” Not “encrypted at rest.” There is no column, no blob path, and no contract field for any of it, at any point in the system.

  • Name
  • Date of birth
  • Address
  • Portrait or photo
  • Document number
  • Wallet identifier
  • Mobile-ID payload
  • Session transcript
  • Patron identifier
  • Repeat-visitor identifier
The entire verification request Unchanged in every release
document type
org.iso.18013.5.1.mDL
namespace
org.iso.18013.5.1
element
age_over_21
intentToRetain
false

Including in error reports

An error is not an exemption. Error payloads from device-ingestion handling exclude raw request bodies and unvalidated fields, and issues are grouped and titled from a bounded set of stage, error type, and failure code — never from a raw message that could carry a row value into a monitoring tool.

04 · Enforcement

Six mechanisms that make the boundary hold.

A boundary maintained by discipline erodes. These are the mechanisms that make the wrong thing hard to build rather than merely discouraged.

No patron entity

There is no primary key anywhere in the schema that represents an individual. Nothing can hold a foreign key to something that does not exist.

Aggregate-only outcomes

The outcome-bearing tables are upsert-a-counter tables, never insert-a-row-per-outcome tables. They are structurally incapable of holding a per-patron record.

Allowlist ingestion

The device-to-platform schema rejects unrecognized fields rather than stripping them. A device sending date_of_birth gets an error — and that attempt is itself observable.

k-anonymity suppression

Rows below a minimum group size of three are withheld in the query and re-checked in responses, exports, and contracts, for every role.

Bounded anti-replay receipts

De-duplication state stores a hash of the random event ID plus a keyed commitment to the payload. It carries no queryable outcome, location, time, reason, or version — so it is not a report in disguise.

Typed metadata, no free-text bag

Event metadata is constrained by a schema keyed to each event type. There is nowhere generic for an engineer to drop a name “just this once.”

05 · The consequence

A breach here cannot expose patron identity.

Not because of how well the data is protected, but because the data is not there. That is the whole argument, and it is the reason we would rather be judged on the schema than on a policy document.

Staff and account data — the people who log into your portal — is a separate, ordinary privacy domain with ordinary rules. This page is about patron data.

Question a reviewer asks Answer
Where is patron PII stored? Nowhere. No column exists.
How long is it retained? Not applicable — it is never received.
Who can access it? No one, including us.
Can a report identify a person? Rows below k = 3 are suppressed before rendering.
Is there a re-identification key? No stable per-person value is derived anywhere.

Next step

Put this in front of your privacy reviewer.

We would rather answer hard questions early than discover them during a rollout.