Skip to content

Annotations

This content is for 0.1. Switch to the latest version for up-to-date documentation.

Core annotation models — the data map vocabulary, storage-agnostic.

Authoring helpers that attach these to concrete ORMs live in effaced.adapters (SQLAlchemy first). The models here are pure data: they validate, serialize, and never import a database library.

class PiiSpec(BaseModel):
category: PiiCategory
description: str | None = None
erasure: ErasureStrategy = ErasureStrategy.DELETE
legal_basis: LegalBasis | None = None
purpose: str | None = None
retention: RetentionPolicy | None = None

Full declaration for one personal-data field.

Built by the adapter authoring helpers (e.g. effaced.adapters.sqlalchemy.pii); read back by effaced.manifest.DataMap.

Fields:

  • category (PiiCategory): What kind of personal data this is.
  • description (str | None): Optional human note for audits and the PII linter.
  • erasure (ErasureStrategy): What happens on Art. 17 erasure. Defaults to DELETE.
  • legal_basis (LegalBasis | None): Why the data is processed at all (Art. 15 metadata).
  • purpose (str | None): Processing purpose, surfaced verbatim in export bundles.
  • retention (RetentionPolicy | None): Required when erasure is RETAIN (and allowed with ANONYMIZE to document why the record itself survives).
class RetentionPolicy(BaseModel):
basis: LegalBasis = LegalBasis.LEGAL_OBLIGATION
duration: timedelta | None = None
reason: str = Field(min_length=1)

Why and how long a value must outlive an erasure request.

Fields:

  • basis (LegalBasis): The lawful basis that overrides erasure.
  • duration (timedelta | None): How long the duty lasts, if bounded. None means indefinite / determined externally.
  • reason (str): Human-readable legal duty (e.g. "§147 AO invoice retention").
class SubjectLink(BaseModel):
is_subject_table: bool
path: str
subject_id_column: str = Field(default='id', min_length=1)

How a table’s records reach the data subject.

A dotted relationship path from the annotated table to the subject table, e.g. "order.user" for an order_items table whose records belong to the user owning the parent order. The subject table itself uses the empty path "".

Fields:

  • is_subject_table (bool): Whether this link marks the subject table itself.
  • path (str): Dotted relationship path; "" marks the subject table.
  • subject_id_column (str): Identifier field on the subject table that callers pass to export/erasure entry points. Defaults to "id".
class SubjectRef(BaseModel):
extra: dict[str, str] = Field(default_factory=dict)
kind: str = Field(min_length=1, max_length=255)
value: str = Field(min_length=1, max_length=255)

Opaque reference to one data subject, passed to resolvers.

Resolvers receive references (e.g. a Stripe customer id), never the subject’s rich PII — the library moves identifiers, not data.

Fields:

  • extra (dict[str, str]): Additional identifiers a resolver may need (string-typed on purpose — refs must stay loggable and PII-light).
  • kind (str): Namespace of the identifier ("stripe", "email"). Refs are routed to the resolver whose name equals the ref’s kind (ADR 0008).
  • value (str): The identifier itself.