Skip to content

Architecture

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.
0012Retention-expiry sweepAnchored durations make retention windows evaluable; the sweep reports and audits, never deletes.
0013Rectification semanticsCategory-keyed corrections applied locally and fanned out via the outbox; the trail never carries values.
0014Restriction of processingAn append-only restriction ledger mirroring consent; effaced ships the flag, never the enforcement.
0015Outbox requeueSupervised path back from abandonment: explicit ids, fresh retry budget, append-first audit.
0016Supabase Storage rides S3 machinerySupabase 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.
0017Versioned docsstarlight-versions freezes a committed 0.1 snapshot of the released API while stage HEAD stays Latest; one snapshot per minor/major release.
0018Resolver conformance suiteA shipped effaced.testing suite every resolver subclasses; protocol shape, idempotency, and error taxonomy proven once.
0019External retention horizons stay out of the sweepThe retention sweep stays database-only; vendor expiry horizons live in the outbox gate and the audit trail, never the RETENTION_EXPIRED report.
0020FastAPI integration layerOne 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.
0021Caller-owned Alembic migrationseffaced ships no migration scripts and never touches alembic_version; callers autogenerate additive migrations against their own target_metadata.
0022Retention-only erasureSystems that can only schedule expiry park the outbox entry until its horizon, then verify; a schedule is never a completion.
0023Backup replay of committed erasuresA restore resurrects locally-erased subjects; replay derives from the surviving audit trail and re-applies the erasures, never guessing.