Consent
This content is for 0.1. Switch to the latest version for up-to-date documentation.
Art. 7 consent — versioned, timestamped, queryable records.
ConsentLedger
Section titled “ConsentLedger”class ConsentLedger: def __init__(consent_records: Table, audit_sink: AuditSink) -> NoneAppend-only consent bookkeeping (Art. 7).
Withdrawal is as easy as granting — both are one call appending one immutable record. Every call is mirrored into the audit trail.
Consent rows are written through the caller’s session and commit with
it; the mirrored audit event goes through the constructor’s sink, which
persists independently (ADR 0006). A failing sink raises out of
record before the caller can commit, so no consent change can
persist unaudited — the converse (an audit event for a consent write
the caller then rolls back) is possible and is the deliberate, evidence-
preserving direction. Do not commit a session after record
raised.
ConsentLedger.history
Section titled “ConsentLedger.history”def history(session: Session, subject_id: str) -> tuple[ConsentRecord, ...]Every consent event for one subject, oldest first.
Args:
- session (
Session): An open database session. - subject_id (
str): Whose history to read.
Returns:
ConsentRecord— The full, unredacted event sequence — this is the Art. 5(2)...— accountability answer.
ConsentLedger.record
Section titled “ConsentLedger.record”def record(session: Session, record: ConsentRecord) -> NoneAppend one consent event (grant or withdrawal).
Mirrors a CONSENT_GRANTED/CONSENT_WITHDRAWN audit event
whose payload carries only the purpose and policy version — never
the record’s source or any other potential PII.
Args:
- session (
Session): An open database session. - record (
ConsentRecord): The event to append. Never updates existing records.
ConsentLedger.status
Section titled “ConsentLedger.status”def status(session: Session, subject_id: str, purpose: str) -> boolWhether the subject currently consents to a purpose.
Derived from the latest record for (subject, purpose); False
when no record exists. “Latest” means greatest recorded_at
(supply distinct, caller-clock timestamps). Exact timestamp ties
resolve to the withdrawing record — when the order of a grant and
a withdrawal is unknowable, effaced assumes consent was withdrawn.
Args:
- session (
Session): An open database session. - subject_id (
str): Whose consent to check. - purpose (
str): The processing purpose.
Returns:
bool— Current consent status.
ConsentRecord
Section titled “ConsentRecord”class ConsentRecord(BaseModel): granted: bool policy_version: str = Field(min_length=1, max_length=255) purpose: str = Field(min_length=1, max_length=255) recorded_at: datetime source: str | None = Field(default=None, max_length=255) subject_id: str = Field(min_length=1, max_length=255)One consent grant or withdrawal, as it happened.
Records are immutable events, not mutable state: current consent status is derived by reading the latest record per (subject, purpose). This is what makes Art. 5(2) accountability answerable — what consent was given, when, and against which policy version.
Fields:
- granted (
bool):Truefor a grant,Falsefor a withdrawal. - policy_version (
str): Version of the policy text the subject saw. - purpose (
str): The processing purpose consented to (e.g."newsletter"). - recorded_at (
datetime): When the event happened (UTC). - source (
str | None): Where the event came from ("signup_form","api"). - subject_id (
str): Whose consent this is.