Context
Ley 21.719 comes fully into force on 2026-12-01, and the point that shapes this design is that it audits operational evidence, not policies. A boolean column sayingemail_opt_in = true proves
nothing: it cannot say when, how, or what the person was actually shown.
So consent is an entity with history. Every grant and every revocation is a row with proof and a
timestamp, and the current state is derived from the latest row. Overwriting is what destroys the
evidence, so overwriting is forbidden.
This also has direct commercial weight: consent defines the marketable contact, which is the
billable base for the whole suite (ADR-014). A revocation reduces next cycle’s bill with no penalty
window — that is a pricing promise implemented here.
Scope (normative)
core.consents: append-only history per (contact, channel, purpose).core.consent_purposes: parametric —transactional,marketing,product.- Proof capture: source, IP, user agent, the text version shown, the timestamp.
- Current-state resolution: the latest row wins.
- The marketable-contact predicate used by metering and by messaging.
- Grant and revoke endpoints, plus a member-facing preference read.
Non-scope (normative)
- Deciding whether to send.
messagingreads consent; the decision lives inresolveChannels. - Suppressions, which are a different thing: consent is what the person chose, a suppression is what the provider or the system imposed after a hard bounce. They must never be conflated.
- The preference-centre UI —
frontend/portal.
Behaviour (normative)
- Consent is append-only. A revocation is a new row, never an update. FORBIDDEN: a mutable boolean anywhere representing consent.
- Every row carries proof: source, captured timestamp, and the identifier of the text version the person was shown. Consent without proof is not consent.
- Current state per (contact, channel, purpose) is the row with the greatest
captured_at. Ties are impossible: the column is timestamptz with microsecond precision plus a monotonic sequence. - Transactional purpose is not opt-out (DEC-E3). A person who bought something receives their receipt. Only a hard bounce suppression stops it.
- Absence of consent is not consent. The default state for marketing is denied, everywhere, with no per-tenant override.
- A revocation takes effect immediately — before the next send decision, not on the next nightly recompute.
- A marketable contact is one with an active grant on ≥1 channel for a non-transactional purpose, and not suppressed. That predicate is defined here once and consumed by metering.
- Consent survives a merge: the survivor inherits the union, and where the two sides conflict the most restrictive wins. Inheriting a grant the person never gave for that record is the exact failure this rule exists to prevent.
- Erasure deletes consent rows with the profile, but the aggregate proof-of-processing record in the audit log survives (DEC-J3).
Data (normative)
API (normative)
Events (normative)
core.consent.granted and core.consent.revoked, both available as outgoing webhooks. A
revocation is one of the few events a tenant almost always wants to mirror into their own systems.
Acceptance criteria (normative)
- Granting then revoking then granting again produces three rows, and the current state is granted.
- No code path updates or deletes a consent row — proven by a schema-level assertion.
- A revocation is honoured by the very next send decision, verified end to end.
- A contact with no consent row is not marketable and receives no marketing message.
- Transactional messages are delivered to a contact who revoked marketing.
- After a merge where one side granted and the other revoked the same channel and purpose, the result is revoked.
- The marketable-contact count matches an independently computed control set over a seeded population of 50 000.
- Negative: no endpoint or configuration allows opting a contact into marketing by default.
Execution
Single slice, synchronous command. The marketable predicate lives inpackages/core as a pure
function so metering and messaging cannot drift apart on it.