Architecture
Monorepo layout
Section titled “Monorepo layout”effaced is a uv workspace monorepo. Each package is its own PyPI distribution with its own version, changelog, and release tag:
| Path | Package | What |
|---|---|---|
packages/effaced | effaced | Core: annotations, manifest, export, erasure, consent, audit, saga, resolver interface |
packages/effaced-stripe | effaced-stripe | First-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>.
One concept per file
Section titled “One concept per file”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).
Sync-first API
Section titled “Sync-first API”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).
ADR index
Section titled “ADR index”Significant decisions are recorded as architecture decision records. Summaries below; full text on GitHub.
| ADR | Title | In one line |
|---|---|---|
| 0001 | uv workspace monorepo | Per-resolver packages keep the core dependency-light and independently versioned. |
| 0002 | stage→main with release-please | Releases are automated end-to-end; nobody pushes tags or edits versions by hand. |
| 0003 | Widened SemVer | Behaviour is API: any change to what gets deleted or exported is MAJOR. |
| 0004 | DCO over CLA | Contributions are signed off per commit; no CLA signup friction. |
| 0005 | Docs engine deferred | Superseded by 0011; docstring discipline was enforced from day one. |
| 0006 | Sync-first session strategy | Sync engine API on the caller’s Session; async only at the resolver/saga edges. |
| 0007 | Erasure plan semantics | When a row may be deleted vs. anonymized in place; conflicts fail loudly before anything runs. |
| 0008 | Ref→resolver matching | A ref’s kind names its resolver; unknown kinds fail loudly, unmatched resolvers are skipped and recorded. |
| 0009 | Erasure execution audit semantics | What the trail records on success, failure, and re-run; re-runs are no-op successes. |
| 0010 | Saga runner semantics | Claiming via FOR UPDATE SKIP LOCKED, lease-based crash recovery, retry/abandonment taxonomy, completion under lock. |
| 0011 | Astro Starlight site | This docs site; API reference generated from docstrings via griffe. |
| 0012 | Retention-expiry sweep | Anchored durations make retention windows evaluable; the sweep reports and audits, never deletes. |
| 0013 | Rectification semantics | Category-keyed corrections applied locally and fanned out via the outbox; the trail never carries values. |
| 0014 | Restriction of processing | An append-only restriction ledger mirroring consent; effaced ships the flag, never the enforcement. |
| 0015 | Outbox requeue | Supervised path back from abandonment: explicit ids, fresh retry budget, append-first audit. |
| 0016 | Supabase Storage rides S3 machinery | Supabase Storage’s S3-compatible gateway reuses effaced-s3’s prefix-guarded export/erasure machinery instead of forking it; no versioning, so deleting current objects is complete erasure. |
| 0017 | Versioned docs | starlight-versions freezes a committed 0.1 snapshot of the released API while stage HEAD stays Latest; one snapshot per minor/major release. |
| 0018 | Resolver conformance suite | A shipped effaced.testing suite every resolver subclasses; protocol shape, idempotency, and error taxonomy proven once. |
| 0019 | External retention horizons stay out of the sweep | The retention sweep stays database-only; vendor expiry horizons live in the outbox gate and the audit trail, never the RETENTION_EXPIRED report. |
| 0020 | FastAPI integration layer | One router over an app-supplied subject dependency; plain def routes on the threadpool, responses are the engines’ own result models, saga drain is a packaged daemon thread. |
| 0021 | Caller-owned Alembic migrations | effaced ships no migration scripts and never touches alembic_version; callers autogenerate additive migrations against their own target_metadata. |
| 0022 | Retention-only erasure | Systems that can only schedule expiry park the outbox entry until its horizon, then verify; a schedule is never a completion. |
| 0023 | Backup replay of committed erasures | A restore resurrects locally-erased subjects; replay derives from the surviving audit trail and re-applies the erasures, never guessing. |