DataTalksClub / DataTalksClub/website
Build the privacy-request aggregate and domain-adapter kernel
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
Authoritative lifecycle hold — dependency reconciliation 2026-08-30
needs grooming; engineering is not authorized. This block supersedes the stale dependency wording below. The artifact gate is: accepted/merged/green #254 source-only slice → accepted/merged/green #281 → PM rewrites #255 against #281 exact paths, schema, versions, and digests → remove needs grooming → engineering lifecycle. #254 may remain open for HUMAN/legal closure; open/closed state alone is not the gate. #283 and #285 may proceed independently after #281 and are not #255 dependencies. No production proof, HUMAN value, adapter, or activation may be inferred.
Parent epic: #64
Authority/register prerequisite: #254
Kernel input prerequisite: #281
Owner privacy direction: #23 (closed)
Product outcome
Provide one domain-neutral privacy orchestration kernel for access, portable export, correction, restriction, objection, and full erasure. The kernel records a privacy request, proof assertion, policy/manifest identity, lifecycle revision, per-domain work, retry-safe progress, and bounded evidence without knowing or storing domain PII. Owning domains join later through a versioned adapter protocol and their own independently groomed issues.
This issue is backend foundation only. It introduces no requester, Studio, admin API, command, provider, processor, artifact-download, retention, erasure-tombstone, or restore surface.
Normative authority
_docs/PROCESS.md: role-separated lifecycle, versioned verification, and sensitive-artifact handling._docs/specs/01-platform-architecture.md,_docs/architecture/app-boundaries.md, and_docs/architecture/shared-primitives.md: one Django deployment; commands own authorization, transaction, revision/idempotency, audit, and durable work; domains mutate only through owning services; network effects occur after commit._docs/specs/06-studio-and-admin-api.md: deny-by-default authorization and future Studio/admin parity; this issue creates neither adapter._docs/specs/07-security-privacy-operations.md: supported rights, deletion propagation, approved #23 defaults, redaction, and restored-backup fencing direction._docs/specs/10-verification-strategy.md: privacy, concurrency, idempotency, migration, audit/redaction, and failure-injection evidence._docs/specs/open-decisions.md, closed #23, and parent #64: full-erasure default, no differential minors treatment, approved provisional periods, narrow already-issued real-name certificate exception, and remaining HUMAN/legal gaps.- #254: the accepted authority/policy and required-domain manifest consumed by this kernel. #255 must not define that register's policy content.
Dependency and delivery disposition
Depends on accepted #254. Engineering must not start #255 while #254 is open because #254 supplies the canonical policy version/digest, required-domain manifest version/digest, allowed right/domain combinations, and approved block/exception codes. The kernel may define and test their opaque interfaces now, but it may not fabricate a production manifest or policy row.
#255 may be delivered after #254 even while later legal/product inputs remain unresolved because every unresolved capability is explicitly fail-closed:
- no production proof verifier is registered here, so neither account nor accountless proof can be asserted through a live adapter;
- accountless identity proof/recovery remains disabled until an authorized versioned verifier contract is accepted;
- no default request, response, escalation, or appeal interval exists; a request whose deadline policy is unresolved cannot start domain work;
- there is no denied/appealed lifecycle and no automatic deadline decision; unresolved cases enter/stay blocked for future authorized handling;
- holds and exceptions can only block work. This issue cannot release a hold, apply a retention exception, choose an erasure disposition, or override full erasure without an accepted #254 code/version and a later authorized management surface.
Application ownership
Create a top-level privacy Django app and update the architecture/app registry accordingly. privacy owns the aggregate, proof-assertion evidence, domain-work orchestration, adapter/verifier protocols and registries, services, and durable handler. It may depend on core and jobs; it must not import accounts, courses, events, email_app, content, studio, api, or provider code.
Do not place privacy-domain behavior in core or jobs. core continues to own generic RevisionedModel, IdempotencyRecord, Operation, and AuditEvent; jobs continues to own dispatch, leases, retry execution, and handler registration.
Exact aggregate contract
privacy.PrivacyRequest (RevisionedModel)
Use a UUID primary key and these bounded fields:
subject_class: exactlyaccountoraccountless;subject_binding_ref: opaque UUID issued by an accepted proof verifier; never an email, username, provider identifier, credential, or raw domain key;status: exactlypending_proof,ready,processing,blocked,completed, orcancelled;policy_version(bounded identifier) andpolicy_digest(lowercase SHA-256);notice_version(bounded identifier);domain_manifest_version(bounded identifier) anddomain_manifest_digest(lowercase SHA-256);deadline_state: exactlyunresolved,resolved, oroverdue;deadline_policy_version,due_at, andescalation_atare nullable as one all-or-none group.resolved/overduerequires all three andescalation_at >= due_at >= received_at;unresolvedrequires all three empty;policy_block_state: exactlyunresolved,clear, orblocked; a request may process only whenclear;- nullable one-to-one
proof_assertionand required one-to-one protectedcore.Operationof kindprivacy.request; - bounded
safe_reason_codefrom the kernel allowlist only; no free-text reason; received_at,created_at,updated_at, nullablecompleted_at, and nullablecancelled_at.
Database constraints require revision >= 1; terminal timestamps match terminal state; nonterminal states have no terminal timestamp; completed and cancelled are mutually exclusive. Application validation rejects an empty rights set, an unrecognized #254 policy/manifest identity, unsafe identifier syntax, and any attempt to store a subject value outside the opaque UUID.
privacy.PrivacyRequestRight
One row per request/right with choices exactly access, portable_export, correction, restriction, objection, and erasure; unique (request, right). Rights are immutable after leaving pending_proof. Do not encode rights in a mutable JSON list or infer one right from another.
privacy.PrivacyProofAssertion
Append-only, UUID keyed, and created only from a validated verifier result:
subject_binding_ref,subject_class, fixed purposeprivacy_request;- bounded
verifier_keyand positiveverifier_contract_version; - lowercase SHA-256
assertion_digestover the verifier's canonical safe assertion identity; never the assertion, token, session key, email, or credential itself; verified_at,expires_at, andconsumed_at, withverified_at <= consumed_at < expires_at;- bounded
assurance_codefrom the verifier definition andcreated_at.
Exactly one assertion may be consumed by one request. The request's subject class/binding must equal the assertion. Proof is revalidated through the registered verifier before ready -> processing; missing, unknown, revoked, expired, replayed, mismatched, or unavailable proof fails without enumeration and cannot advance the request. No default verifier is registered in this issue; tests use explicitly test-only definitions.
privacy.PrivacyRequestBlock
Append-only block evidence with UUID, request, block_type (policy, legal_hold, exception, deadline, adapter, proof), bounded accepted policy_code, policy_version, SHA-256 evidence_digest, domain_key optional, active_from, nullable resolved_at, and safe resolution code. It stores no reason text or source document. An active or unknown block prevents affected work. Resolution is structurally supported by the service but has no production caller in this issue; legal-hold/exception resolution additionally rejects any code/version absent from accepted #254 authority. Exceptions are blockers awaiting an approved disposition, never implicit authorization to retain or anonymize data.
privacy.PrivacyDomainWork (RevisionedModel)
One row per required domain in the pinned manifest:
- UUID, request FK, bounded
domain_key, positiveadapter_contract_version, and immutablemanifest_entry_digest; - state exactly
pending,prepared,running,retry_wait,blocked,succeeded, orcancelled; - positive
execution_generation,attempt_count, and boundedmax_attempts(1..100); - nullable SHA-256
input_digest,result_digest, andevidence_digest; - nonnegative
item_countand bounded numeric-onlysafe_counts; no strings, domain field names, record identifiers, or arbitrary adapter metadata; - bounded allowlisted
result_codeandlast_error_code, nullablenext_attempt_at, timestamps, and nullable protected latestDurableJobreference.
Unique (request, domain_key). prepared/later requires input_digest; succeeded requires all result/evidence digests and no retry time; retry_wait requires a retry time; terminal request completion requires every required domain row succeeded. A missing/unknown domain, adapter/version/capability mismatch, manifest drift, unsupported right, or extra unlisted adapter blocks before any adapter execution. No required domain may be skipped or marked not-applicable by runtime inference.
privacy.PrivacyDomainWorkRight
One immutable row per domain work/right; unique (domain_work, right). It is populated only from the exact accepted #254 manifest entry and must be a subset of both request rights and the registered adapter's declared capabilities.
Lifecycle and transition table
Only the shared privacy application service may transition rows. Every mutation requires current authorization, expected request/work revision as applicable, and a required idempotency key.
| From | To | Allowed condition |
|---|---|---|
| create | pending_proof |
accepted policy/manifest identity is pinned; rights valid; core.Operation and create audit are atomic |
pending_proof |
ready |
one unexpired, unconsumed, purpose-bound assertion matches subject class/binding; verifier is registered and currently validates it |
pending_proof |
cancelled |
no domain work exists |
ready |
processing |
proof revalidates; deadline is resolved and not overdue; policy block is clear; exact manifest and every adapter/version/right match; all domain-work rows and durable intents are persisted atomically |
ready |
cancelled |
no work has been prepared or executed |
ready/processing |
blocked |
proof/policy/deadline/manifest/adapter invariant fails, an active hold/exception exists, or a domain reaches a non-retryable/exhausted safe failure |
blocked |
processing |
explicit idempotent resume; every blocking invariant is rechecked and no unresolved/active block remains; a new execution generation is created only for affected work |
processing |
completed |
every manifest-required domain work is succeeded; operation succeeds in the same transaction |
processing |
cancelled |
only before any work reaches prepared, running, or succeeded; otherwise cancellation fails closed |
All other transitions are invalid. completed and cancelled are terminal. There is no denied, appealed, automatic approval, automatic exception, automatic cancellation, or terminal “partial success” state. Deadline expiry changes a nonterminal request to blocked/overdue; it does not decide the right or emit an appeal. An adapter failure never causes another domain to be silently treated as complete.
Verifier and domain-adapter protocols
Definitions are immutable, code-registered dataclasses. Duplicate keys with different definitions, malformed keys, unknown versions, and import-order disagreement fail startup/system checks. Registry enumeration is deterministic.
PrivacyProofVerifier declares key, positive contract_version, supported subject classes, bounded assurance codes, and two side-effect-free methods: consume/validate a canonical proof assertion into the safe PrivacyProofAssertion DTO, and revalidate that stored DTO at processing time. It returns only opaque UUIDs, versions, timestamps, codes, and digests. No production verifier implementation belongs to #255.
PrivacyDomainAdapter declares domain_key, positive contract_version, and supported rights. It exposes:
prepare(context) -> DomainWorkPlan: side-effect-free; returns the exact immutable input digest, nonnegative item count, and numeric safe counts for the pinned request/policy/manifest/subject-binding identity.execute(context, plan) -> DomainWorkResult: calls only the owning domain's future application service, using the stable domain-work UUID plus execution generation as its domain idempotency identity; returns only allowlisted result code, numeric safe counts, and result/evidence digests.reconcile(context, plan) -> DomainWorkResult: used after an ambiguous crash boundary to obtain the same committed result or a safe retry/block decision; it never blindly repeats a non-idempotent mutation.
Context contains only request/work/operation UUIDs, subject-binding UUID, rights, pinned policy/manifest identities, execution generation, correlation/job IDs, and lease token. It contains no actor credentials, PII, domain record IDs, export content, provider payload, or free-form reason. Future export/correction artifact content and domain locators remain owned by separate issues and must not be returned through the kernel's JSON evidence.
#255 registers only the kernel durable handler and explicitly test-only fake verifier/adapters in tests. It registers no accounts/courses/events/email/content/search/audit/provider production adapter and does not absorb #251.
Transactions, idempotency, durable retry, and concurrency
- Reuse
core.IdempotencyRecord; do not create a second idempotency table. Scopes are stable per privacy command and authorized actor/subject binding. Store only the fenced key hash and canonical request hash. Exact replay returns the original bounded safe DTO; the same key with changed action/request/revision/arguments conflicts; in-progress ownership and fence loss fail safely. - Request creation atomically creates request, rights,
core.Operation, idempotency ownership/result, and append-only audit. Start atomically validates the frozen manifest/registry, creates all domain-work/right rows, advances request/operation, persists onejobs.DurableJobintent per work, and completes idempotency before after-commit wakeups. - Durable handler name is versioned (for example
privacy.domain_work.v1). Payload contains only request/work/operation UUID and execution generation. Deduplication identity is stable for(work UUID, generation); payload or version drift conflicts. - Preparation is side-effect-free. The worker persists the prepared digest with optimistic revision control, then invokes the adapter. A domain mutation must be idempotent on
(work UUID, generation, input digest). If a crash occurs after a domain commit but before the kernel result commit, retry callsreconcilewith the same identity before any execute retry. - Adapter execution never performs network I/O in the kernel/domain transaction. Any future network effect must be a domain-owned durable intent created in that domain service transaction and use its stable idempotency identity after commit.
- Worker completion re-reads proof, request, active blocks, manifest, adapter definition/version, input digest, lease, and work revision. Drift or lease/revision loss cannot commit success. Kernel work result, safe progress, request/operation state, and audit are committed atomically.
- Retry uses
DurableJob's fenced lease and bounded attempts. Only explicit transient safe codes enterretry_wait; permanent, unknown, ambiguous-after-reconcile, or exhausted failures block the work/request. Manual resume requires an idempotent command, expected revision, all invariant checks, and a new generation. It never edits or reuses an old job intent. - SQLite correctness relies on portable declarative constraints and optimistic conditional updates, not
select_for_update; PostgreSQL may lock for efficiency but must preserve the same outcomes.
Authorization, audit, and security
There are no HTTP/Studio/API/command adapters in this issue. Services require an injected registered authorization policy; absence is deny-by-default. Test policies prove subject-scoped and privileged-system behavior. Actor references are attribution snapshots only and never authorization input. Worker/system authorization is limited to one request/work and rechecked from persisted state.
Every create, proof success/failure, transition, block/resolution attempt, prepare, execute/reconcile result, retry, cancellation, stale/conflict/denied attempt, and terminal outcome creates core.AuditEvent through the shared writer. Use stable action names under privacy.request.* and privacy.domain_work.*, opaque target IDs, standard request/correlation/job/idempotency hashes, safe before/after state/revision, domain key, adapter/policy versions, counts, digests, and allowlisted reason/result codes only.
Never persist or emit names, emails, usernames, IPs, accountless contact values, tokens, credentials, session/cookie values, raw proof, domain record IDs, field values, export content, provider payloads, exception/hold prose, or reversible PII. Run shared redaction before bounded canonical serialization; reject (do not merely redact and accept) any protocol result with unknown keys/types, strings outside code allowlists, sensitive-key names, URLs/emails/credential shapes, excessive depth/items/bytes, NaN, or noncanonical data. Exceptions and logs expose stable safe error codes only. Issue comments/test artifacts use synthetic opaque IDs and canaries.
Scope
- Add the
privacyapp, exact models/constraints/migrations above, application services, DTOs, verifier/adapter registries and system checks, authorization seam, and versioned durable handler. - Reuse
core.Operation,IdempotencyRecord,AuditEvent, revision conflicts, redaction, execution context, andjobs.DurableJobrather than duplicating them. - Add architecture/app-boundary and internal developer documentation for the kernel contract only.
- Add model/service/protocol/worker/security/migration tests with synthetic non-PII fixtures.
Explicit non-goals
- No production proof verifier or account/accountless intake/recovery; no email link, OTP, session reauthentication, identity lookup, or rate-limit product policy.
- No requester/self, Studio, admin API, public API, management command, OpenAPI, URL, template, navigation, static asset, email, notification, or browser behavior.
- No domain PII queries/mutations or production adapters; no #251 absorption; no accounts-to-courses import.
- No export artifact/content, correction implementation, retention runner, erasure/anonymization implementation, tombstone/restore fence, cache/search invalidation, processor/Relay/provider call, receipt, or production data.
- No legal rule, deadline interval, escalation/appeal outcome, hold release, exception disposition, lawful basis, notice wording, retention exception, or processor policy invention.
- No commit, push, merge, deployment, protected-data access, production execution, or external side effect outside the normal issue lifecycle.
Acceptance criteria
- A dedicated
privacyapp owns the kernel and the architecture dependency diagram remains one-way; import-boundary tests proveprivacydoes not import any domain, presentation, worker-task, or provider module. - The exact aggregate, right, proof, block, domain-work, and domain-work-right models/constraints above exist; unsafe combinations fail on SQLite and production-like PostgreSQL.
- The transition table is exhaustive: every allowed edge has success/replay/stale-revision/concurrent tests and every other edge fails without mutation; terminal states cannot reopen.
- Account and accountless subject classes are structurally supported through opaque bindings, but the absence/unknown/expired/replayed/mismatched/unavailable verifier denies advancement non-enumeratingly; no live verifier is registered.
- Unresolved deadline, overdue deadline, unresolved policy, active hold/exception, unknown #254 code/version, missing/extra/unknown adapter, version/capability/manifest mismatch, and unsupported rights all block before domain execution.
- Exact idempotent command replay returns the original safe result; changed payload/revision/action conflicts; concurrent create/start/resume/cancel yields one authoritative outcome, one work set/generation, and no duplicate durable intent.
- Empty, one, and many required domains; zero/one/many rights; partial success; transient retry; retry exhaustion; permanent failure; timeout; crash before domain commit; crash after domain commit/before kernel commit; reconciliation; worker restart; stale lease; and resume prove no skipped domain or duplicate mutation.
- Each adapter invocation is pinned to subject-binding/request/work/operation IDs, policy/manifest/adapter versions, rights, generation, and input digest; changed input or registry state fails closed and cannot reuse evidence.
- Operation progress/result and request/domain state remain consistent, bounded, revisioned, and atomically audited across success, block, retry, cancel, and failure injection.
- Audit, logs, exceptions, job payloads, operation summaries, idempotency results, fixtures, migration output, and verification artifacts pass explicit PII/credential/token/URL/provider-payload canaries and contain only the allowed opaque IDs/codes/counts/digests.
- New migrations pass forward, reverse, fresh install, and migrate-back/migrate-forward (“apply twice”) checks on SQLite and production-like PostgreSQL; migration drift is clean.
- Focused privacy/core/jobs tests, import/system checks, formatting/lint/type checks, container tests, and the graph-selected versioned verification plan pass. Property/state-machine and real concurrent transaction tests cover transitions/idempotency on both supported databases.
- Render contract is exactly no templates, URLs, navigation, static assets, context processors, OpenAPI, or response changes. The independent tester records Playwright/screenshots as
not_applicableonly when the final change graph proves no render path; otherwise the graph-selected browser tier and desktop/mobile screenshots are mandatory.
Required verification scenarios
- Accepted versus unknown policy/manifest versions and digests; duplicate/missing/extra domain rows; adapter registry order; conflicting registration; startup/system-check failure.
- Account/accountless proof missing, valid test assertion, expired, revoked/unavailable, replayed, wrong purpose/class/binding, and proof expiring between ready and start; all denial output is indistinguishable and safe.
- Every lifecycle edge, invalid edge, expected-revision race, duplicate idempotency key with same/different request, two simultaneous starts, two workers, stale lease, cancellation before/after preparation, and terminal replay.
- Empty/one/many domain manifests; every right; mixed adapter capabilities; partial completion; transient/permanent/unknown error; retry timing/attempt bound; reconcile-after-ambiguous-crash; resume generation and old-job fencing.
- Active/unresolved/resolved test-only policy block, hold, exception, and deadline records; unknown authority code; due/escalation ordering; overdue behavior; prove none selects a legal disposition or appeal.
- Canary strings resembling email, URL, bearer/JWT/GitHub/AWS credentials, cookie/session/token, domain values, nested provider payload, cycles, excessive JSON, Unicode/control characters, NaN, and free-form error text at every protocol/audit/job/result boundary.
- Migration constraint introspection plus forward/reverse/fresh/apply-twice on SQLite and PostgreSQL; crash/rollback injection proves aggregate/work/audit/job/idempotency atomicity.
Browser and screenshot disposition
Expected: not_applicable. #255 must have no rendered route or API response. The engineer records pending_independent_tester until the independent tester recomputes the final graph. Only that tester may mark Playwright/screenshots not_applicable; any actual render impact restores the normal browser and inspected desktop/mobile screenshot gate.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Do not begin implementation: the issue is explicitly blocked for grooming and depends on accepted #254 and #281. First read _docs/PROCESS.md, the listed architecture and verification documents, then review those prerequisite issues. The work is done only after PM rewrites #255 with exact paths, schemas, versions, and digests and removes the grooming block.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- django, python
- Domain
- backend, backend-api-design, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 15/100