Skip to content

Architecture

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

effaced is a uv workspace monorepo. Each package is its own PyPI distribution with its own version, changelog, and release tag:

PathPackageWhat
packages/effacedeffacedCore: annotations, manifest, export, erasure, consent, audit, saga, resolver interface
packages/effaced-stripeeffaced-stripeFirst-party Stripe resolver
site/This docs site (Astro Starlight, pnpm) — inside the repo, outside the uv workspace

Resolvers live in separate packages so the core stays dependency-light: a user without Stripe never installs the Stripe SDK, and a Stripe SDK bump never forces a core release (ADR 0001). Future resolvers follow the same shape: packages/effaced-<name>.

The codebase is deliberately small-grained: one class per file, the file named after the class it holds (pii_spec.py holds PiiSpec), a hard 600-line cap per file enforced in CI, and packages over modules (erasure/{plan,planner,result}.py rather than one erasure.py). The payoff for users: everything is greppable, and the import path tells you where a concept lives.

The same discipline extends to typing (mypy --strict, zero errors) and data modeling (frozen pydantic models with validated invariants).

The public engine API is synchronous: Exporter.export_subject, erase_subject, and the ConsentLedger methods are plain def taking your open SQLAlchemy Session as the first parameter. async def appears only where I/O is inherently async — Resolver protocol methods and SagaRunner.run_once.

Rationale (ADR 0006): effaced does not own its database I/O — it runs on a caller-provided session, and sync Session is the universal denominator. Sync apps (Flask, Django, scripts, cron) are first-class with zero configuration; async apps pay one documented line per call site (run_in_threadpool, or a plain def route).

Significant decisions are recorded as architecture decision records. Summaries below; full text on GitHub.

ADRTitleIn one line
0001uv workspace monorepoPer-resolver packages keep the core dependency-light and independently versioned.
0002stage→main with release-pleaseReleases are automated end-to-end; nobody pushes tags or edits versions by hand.
0003Widened SemVerBehaviour is API: any change to what gets deleted or exported is MAJOR.
0004DCO over CLAContributions are signed off per commit; no CLA signup friction.
0005Docs engine deferredSuperseded by 0011; docstring discipline was enforced from day one.
0006Sync-first session strategySync engine API on the caller’s Session; async only at the resolver/saga edges.
0007Erasure plan semanticsWhen a row may be deleted vs. anonymized in place; conflicts fail loudly before anything runs.
0008Ref→resolver matchingA ref’s kind names its resolver; unknown kinds fail loudly, unmatched resolvers are skipped and recorded.
0009Erasure execution audit semanticsWhat the trail records on success, failure, and re-run; re-runs are no-op successes.
0010Saga runner semanticsClaiming via FOR UPDATE SKIP LOCKED, lease-based crash recovery, retry/abandonment taxonomy, completion under lock.
0011Astro Starlight siteThis docs site; API reference generated from docstrings via griffe.