DataTalksClub / DataTalksClub/website
Define generation-bound email outbox reconciliation receipts
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
Parent delivery issue: #49
Blocked restore consumer: #258
Backup-proof prerequisite: #264
Product outcome
Publish one immutable, machine-validated non-activating email outbox reconciliation contract for restored website state. It defines the exact generation identity, complete historical-outbox selection envelope, redacted reconciliation receipt and pure current-state evaluator that future #49 delivery code must implement and #258 may consume.
Version 1 cannot select a real row, query Relay, reconcile a delivery, release a worker, or authorize restore activation. It records the structure and fail-closed vocabulary only, with every production adapter, verifier, transport contract, and execution bound explicitly null. Synthetic tests may exercise an in-memory contract fixture that is unreachable from application/runtime code.
Why this is separate
#49 owns normal EmailDelivery intents, durable jobs, redacted Relay projections, callbacks, and scheduled/manual reconciliation. Its current issue specifies those runtime behaviors but does not publish a generation-bound recovery definition, selection proof, reconciliation receipt, state arithmetic, or current-state evaluation interface for #258.
#264 owns provider-neutral backup verification and its immutable BackupVerificationReceipt; it does not inspect or classify email work. #281 and #283 establish the source-only/non-activating pattern for privacy kernel and retention inputs, but privacy does not own email state semantics. #258 must consume an email-owned result rather than inventing an outbox adapter or treating a synthetic adapter as production evidence.
This issue supplies only that missing source interface. It neither implements #49 nor enlarges #258, #264, #281, or #283.
Normative authority
_docs/PROCESS.md: issue lifecycle, immutable verification evidence, role separation, and source-only screenshot disposition._docs/specs/01-platform-architecture.md,_docs/architecture/app-boundaries.md, and_docs/architecture/shared-primitives.md:email_appownership, after-commit durable work, fenced at-least-once execution, and no provider work in a transaction._docs/specs/05-events-registration-email.md: exact website projection states, Relay authority, callback/reconciliation boundary, request-hash idempotency, ambiguity without automatic resend, and no Datamailer/direct-SES fallback._docs/specs/07-security-privacy-operations.md: restore holds historical email work until reconciliation proves it safe; safe evidence and no historical resend._docs/specs/09-migration-rollout-roadmap.md: immutable release identity, worker/send holds, rollback reconciliation, and no dual sender._docs/specs/10-verification-strategy.md: callback/reconciliation fault cases, restored-backup hold, redaction, and no duplicate delivery.- Resolved email decision in
_docs/specs/open-decisions.mdand #21: Relay is the sole canonical transport owner; accepted is not delivered and ambiguity never authorizes automatic resend. - #49: future runtime producer and owner of delivery semantics.
- Accepted source-only delivery of #264: sole backup requirements/runtime/schema/snapshot/result/receipt identity consumed by this contract.
Dependency gate
Engineering depends on #264 passing independent testing, PM acceptance, merge/push, and green on-call. This contract pins the exact accepted BackupVerificationReceipt schema/version/API and source commit. A frozen candidate, issue prose, mutable branch/tag, worktree path, or provisional digest is not a consumable identity.
#49 is not an implementation prerequisite for this non-activating artifact; this issue is a prerequisite to re-grooming and implementing the recovery-specific portion of #49. #281 and #283 are neither prerequisites nor authorities for email state. #258 remains in grooming until this issue and the other prerequisites named by its audit are accepted and green.
Any accepted #264 identity/API drift invalidates this candidate and requires a new contract version and fresh verification. Any future #49 state, selector, adapter, or Relay contract that cannot satisfy this artifact requires re-grooming this issue rather than local reinterpretation.
Canonical files and ownership
This issue owns exactly four new source files:
_docs/architecture/email-outbox-reconciliation-contract.json— sole hand-edited canonical contract;_docs/architecture/email-outbox-reconciliation-contract.schema.json— JSON Schema Draft 2020-12 structure;scripts/validate_email_outbox_reconciliation_contract.py— read-only canonical loader, semantic/cross-contract validator, and pure synthetic evaluator;scripts/tests/test_email_outbox_reconciliation_contract.py— identity, state, arithmetic, retry/idempotency, drift, non-activation, and redaction tests.
It modifies no #49/#258/#264/#281/#283 artifact and creates no generated copy, Python runtime constant, settings value, database seed, migration, fixture discoverable by production, registry plugin, command, route, job, callback, or mutable latest alias.
Canonical identity
The JSON has exactly these top-level sections:
schema_version: integer1;contract_versionandcontract_digest;authority_inputs;activation;generation_identity_contract;outbox_state_catalog;selection_contract;reconciliation_result_catalog;reconciliation_reason_catalog;retry_idempotency_contract;production_contract_slots;receipt_contract;current_state_evaluator_contract;synthetic_fixture_contract.
contract_version is a bounded immutable ISO-date-plus-positive-revision identifier. contract_digest is lowercase SHA-256 over UTF-8 NFC canonical JSON with sorted object keys, compact separators, preserved Unicode, integers only, and no BOM; only contract_digest is omitted from its own digest. Arrays have one semantically validated canonical order. Duplicate keys/identities, unknown fields, alternate bytes/order, mutable aliases, floats/NaN, or a reused version with changed bytes fail.
authority_inputs pins the exact accepted #264 module path, schema/verifier contract versions, safe DTO/evaluator symbol names, accepted full source commit, and a deterministic definition digest over that interface. The validator imports/loads only that accepted repository source and rejects missing symbols, unsupported versions, source/API drift, or any reverse dependency from #264 into email/privacy code.
Exact non-activation contract
activation is fixed in version 1:
state:non_activating;production_selection_allowed:false;production_reconciliation_allowed:false;production_receipt_allowed:false;production_adapter_registration_allowed:false;provider_or_relay_access_allowed:false;worker_or_send_release_allowed:false;restore_activation_authority:false;runtime_loading_allowed:false;synthetic_test_loading_allowed:trueonly through the validator's explicit in-memory test seam;superseded_by:null.
No setting, environment variable, database row, command option, import discovery, plugin, fixture, fallback, or default can change those values. Application modules may not import this validator or artifact. Tests must prove no settings/startup/URL/model/migration/service/job/callback/worker/email/provider/render path reaches it.
Generation identity contract
A future reconciliation generation is immutable and contains only:
- schema version
1, opaquerestore_event_refUUID, and positive integergeneration; - the exact accepted #264
requirements_digest,receipt_digest,snapshot_identity_digest,snapshot_manifest_digest,runtime_identity_digest,database_schema_identity_digest, andrecovery_point_atcopied from one currently verifiedBackupVerificationReceipt; - exact email outbox definition key/version/digest, selector definition version/digest, reconciler definition version/digest, and current-state probe definition version/digest supplied by a future activating successor;
- an exact selection-boundary version plus canonical selection-scope digest;
- the resulting domain-separated
generation_identity_digest.
The identity includes no path, table/query/filter, cursor, recipient, email, message/provider ID, body/context, job payload, credential, endpoint, raw backup identifier, or provider response. Version 1 supplies no concrete generation and leaves every production definition/version/digest field unavailable through the null slots below. A generation cannot be inferred from wall-clock time, current branch, mutable runtime state, or #258 data.
Exact replay of one generation identity is stable. Any changed restore ref/generation, #264 receipt or nested identity, outbox/selector/reconciler/probe definition, selection boundary, or scope digest is a distinct generation and cannot reuse a plan, batch, receipt, or idempotency result.
Outbox-state catalog
The catalog recognizes exactly the #49 website projection states:
pendingqueuedleasedprovider_accepteddeliveredretryableambiguoussuppresseddeadhard_bouncedcomplained
Each row records only structural properties: whether transport may have begun, whether the state is terminal in the normal #49 projection, whether current Relay reconciliation is required, and whether automatic resend is forbidden. Version 1 sets restore_safe_without_proof: false and production_success_allowed: false for every state. It does not guess that any observed projection is safe after a restore.
provider_accepted is not rewritten as delivered. ambiguous always requires reconciliation and forbids automatic resend. A terminal-looking website projection is not sufficient without the future #49 current-state proof. Unknown, missing, callback-lagged, changed-request, incompatible-version, and corrupt observations are reasons to block, not extra states or safe defaults.
Complete selection contract
A future selector must prove complete coverage of every restored website-owned logical EmailDelivery intent, linked durable job, and redacted Relay projection in the exact generation scope, including rows that appear terminal. Selection is based on the accepted email definition and restored snapshot boundary, never on a caller-supplied state allowlist, arbitrary cutoff, mutable query, or a “currently unsafe only” shortcut.
The source contract defines only the safe envelope:
- generation identity digest;
- selection definition/version/digest and selection-scope digest;
- nonnegative bounded
selected_count; - ordered input-manifest digest over opaque per-item reconciliation identities;
- exact state-count map containing all 11 catalog keys, including zeroes;
selection_result: exactlycomplete,incomplete, ordrifted;- one bounded reason code;
- plan identity/digest.
A complete empty selection is representable only when the future accepted selector proves the exact scope contains zero applicable items; absence of an adapter/query/result never means empty. selected_count must equal the sum of all state counts. No row identifiers or item list enter retained evidence. Unknown/duplicate/omitted work, count/digest disagreement, definition drift, or selection that changes between plan and reconciliation blocks and requires a new generation.
Reconciliation results, reasons, and arithmetic
The exact per-item result catalog is:
held_safe— selected work is durably prevented from dispatch and the future accepted #49 contract proves the hold;reconciled_safe— the future accepted #49 reconciliation/current-state proof establishes a state that cannot dispatch or automatically resend;blocked_unsafe— safety is absent, ambiguous, incomplete, or drifted.
All three set production_result_allowed: false in version 1. No result is emitted by the canonical loader. A future activating contract must define exact state-to-result and proof rules; this artifact intentionally contains no universal “terminal state equals safe” mapping.
The exact bounded reason catalog is:
contract_not_activatedbackup_receipt_missingbackup_receipt_mismatchgeneration_mismatchdefinition_unavailabledefinition_mismatchselection_incompleteselection_driftstate_unknownstate_changedrequest_hash_changedcallback_laggedrelay_state_unverifiedhold_unverifiedresult_ambiguousreconciliation_transientreconciliation_permanentretry_exhaustedcount_mismatchunsafe_evidence
Codes are diagnostic identifiers only. They do not classify a Relay/provider response, resolve ambiguity, permit retry/resend, declare a hold effective, or grant activation. Unknown/custom/free-form codes fail.
Receipt arithmetic is exact and mutually exclusive:
selected_count = held_safe_count + reconciled_safe_count + blocked_unsafe_count
A structurally reconciled synthetic receipt additionally requires blocked_unsafe_count == 0, every selected item accounted for once, and all state/result/reason counts to reconcile to the same selection/input/result manifest digests. Zero counts are valid; negative, floating, excessive, duplicated, omitted, or overlapping counts fail. Production receipt creation remains forbidden.
Retry and idempotency boundary
The contract defines identities, not an executor:
- plan key: generation identity digest + selector definition digest + selection-scope digest;
- batch key: generation identity digest + plan digest + reconciler definition digest + opaque batch UUID + input digest;
- receipt key: generation identity digest + plan digest + complete ordered checkpoint/result-manifest digest;
- evaluation key: receipt digest + exact current #264 requirements/receipt identity + current outbox-state proof digest.
Exact replay returns the original safe synthetic result. Reusing a key with changed action, generation, definition, scope, plan, batch input, result, current-state proof, count, or bound conflicts. Retry is structurally permitted only for an explicit pre-invocation transient or exact replay/reconciliation of the same operation identity. Timeout or loss after invocation may have occurred becomes blocked_unsafe/result_ambiguous until the future #49 service reconciles it.
Nothing in this contract submits, retries, resends, cancels, suppresses, changes a delivery/job/projection, or contacts Relay. Manual resend is outside the contract and, under #49, must create a separate audited logical delivery. ambiguous, unknown, and changed-request work can never be converted to safe by retry count exhaustion or by creating a new key.
Null production contract slots
Version 1 has one email outbox slot with all of these fields exactly null:
outbox_definition_versionandoutbox_definition_digest;selector_contract_versionandselector_definition_digest;reconciler_contract_versionandreconciler_definition_digest;current_state_probe_contract_versionandcurrent_state_probe_definition_digest;relay_api_contract_versionandrelay_openapi_digest;batch_limit,max_attempts,plan_ttl_seconds, andreceipt_ttl_seconds.
Its registration state is blocked_unimplemented, and every production registration/execution flag is false. Null is permitted only in these named disabled fields. The schema cannot represent a callable/import path, model/query name, endpoint, credential/secret, sender, recipient, provider identifier, payload, raw message ID, body, or free-form option. A later activating successor may fill values only after #49 and Relay contracts are accepted through separate grooming; runtime configuration cannot fill them.
Receipt and pure current-state evaluator
The receipt schema contains only the exact generation/plan/selection/definition identities and digests; opaque receipt UUID; state exactly blocked, reconciling, reconciled, failed, or superseded; the three result counts and complete state/result/reason count maps; ordered checkpoint/result-manifest digest; explicit UTC completion instant; safe reason code; and domain-separated receipt digest.
Version 1 permits canonical source validation and synthetic in-memory receipts only. reconciled means solely that the synthetic arithmetic/proof fixture passed; it is not permission to resume a worker or activate a restore.
evaluate_current_outbox_safety(receipt, generation_identity, backup_requirements, backup_receipt, current_state_proof, evaluated_at) is a pure source-test seam returning only blocked or reconciled. It first calls #264's accepted receipt evaluator, then revalidates every generation/definition/plan/receipt digest, exact state/result arithmetic, current selection count/manifest, zero unresolved work, and current-state proof identity. Missing/corrupt/stale/superseded evidence, any new/changed selectable work, changed state/request hash, null production slot, ambiguous/unverified item, or time/identity drift returns blocked with a bounded reason and no unsafe value.
Even reconciled is not activation authority: #258 must pin and revalidate the eventual production receipt inside its own fence/generation, and #66 owns any later workload/startup release. The evaluator exposes no side effect, adapter lookup, last-known-good fallback, warning mode, override, or force option.
Sealed synthetic test seam
The canonical contract includes a separately digested synthetic_fixture_contract describing one reserved synthetic_test_only generation, outbox definition, selector, reconciler, probe, and bounded in-memory fixtures. The explicit test loader requires the canonical non-activating contract and unforgeable test marker, fills only reserved test_ identities and small bounds, and cannot serialize its enabled object as the canonical artifact.
It rejects production/current repository identifiers, real model/table/query names, #49 delivery IDs, #264 non-synthetic receipts, environment/settings input, filesystem overrides, registry discovery, and any non-synthetic value. It performs no Django setup, database/file discovery, network/DNS/socket/subprocess/provider/Relay call, mutation, or file write. Static import tests fail if a non-test application module imports the test loader or validator.
Validator and security behavior
The validator is read-only and fails closed on:
- schema/version/digest/canonical-byte/order/duplicate/unknown-field errors;
- #264 path/API/version/source/digest drift or a receipt/evaluator incompatibility;
- any active/allowed/runtime-loaded value, non-null production slot, missing null field, default/fallback/wildcard, callable path, registry hook, or production-like fixture;
- missing/extra/reordered/duplicate state/result/reason/slot fields;
- generation, scope, plan, batch, result, receipt, current-state, count, arithmetic, or idempotency drift;
- treating accepted as delivered, terminal as restore-safe without proof, ambiguity as retryable/safe, changed request as replay, missing adapter/result as empty, or a receipt as activation authority;
- names, emails, usernames, IPs, subject/domain/database/message/job/provider IDs, reversible hashes, paths/cursors/queries, URLs/endpoints/ARNs, credentials/tokens/cookies/sessions, raw provider/backup payloads, message content/body/context, SQL, stack traces, free-form evidence, noncanonical Unicode/control text, or production values.
Tests use only opaque synthetic UUIDs/digests/canaries. Validator/evaluator output is deterministic and bounded to safe paths, catalog IDs, counts, versions, digests, and blocked|reconciled; rejected input is never echoed.
Scope
- Add only the exact four source files.
- Bind the recovery generation identity to the exact accepted #264 receipt/evaluator contract.
- Define all 11 #49 projection states without assigning unapproved restore-success semantics.
- Define complete selection, exact result/reason catalogs, arithmetic, replay/conflict identities, redacted receipt, pure evaluator, null production slots, and sealed synthetic fixtures.
- Supply the stable email-owned artifact required to re-groom #49's recovery portion and #258.
Explicit non-goals
- No modification of #49/#258/#264/#281/#283 source, issue authority, candidate, report, schema, or accepted digest.
- No
email_appmodel/migration/service/registry/setting/URL/view/template/static/OpenAPI/Studio/admin/command/job/scheduler/callback/worker/runtime code. - No production/test database, EmailDelivery/durable-job row, recipient, protected data, backup/restore, Relay/Datamailer/SES/provider/network call, callback, submission, retry, resend, suppression, state transition, hold, or activation.
- No provider/Relay endpoint, credential, sender/purpose/template approval, API/OpenAPI version invention, terminal-state mapping, retry/batch/TTL choice, legal/privacy/retention/processor decision, or HUMAN authority inference.
- No production adapter/verifier/current-state probe, fake adapter discoverable outside tests, production-like fixture, runtime loader, fallback, override, or
forcepath. - No browser/render change, commit, push, merge, deployment, or external operation outside the normal lifecycle.
Acceptance criteria
- The exact four files exist with one hand-edited canonical contract and no shadow manifest, generated copy, runtime constant, registry, or alternate loader.
- Contract/synthetic identities and digests are canonical, deterministic, immutable, and pin exact accepted/merged/green #264 path/API/version/source identity.
- Activation is exactly non-activating; every production selection/reconciliation/receipt/registration/provider/worker/restore/runtime flag is false and cannot be changed through settings, environment, database, imports, fixtures, defaults, or plugins.
- Generation identity contains exactly the safe #264 receipt/snapshot/runtime/schema identities plus versioned email definition/scope digests; any input drift creates a new generation and invalidates plan/batch/receipt reuse.
- All 11 #49 projection states are present exactly once, preserve accepted-versus-delivered and ambiguity/no-resend semantics, and grant no restore-safe or production-success state in version 1.
- Complete selection covers all applicable restored intents/jobs/projections, includes all 11 state counts, enforces exact totals and manifest identity, and never turns unavailable/incomplete/drifted evidence into empty work.
- The three result codes, 20 reason codes, and exact arithmetic are complete and bounded; unknown/free-form values, overlap, partial accounting, and universal state-to-safe mappings fail.
- Plan/batch/receipt/evaluation idempotency keys give exact replay or conflict; possible post-invocation ambiguity cannot retry, resend, exhaust into success, or change identity.
- Every production definition/adapter/probe/Relay/bound field is present and null/disabled; no path/callable/endpoint/credential/provider/message field can be represented or populated at runtime.
- Receipt validation and the pure current-state evaluator revalidate #264, generation, complete selection, definition, arithmetic, current-state, and drift; return only bounded blocked/reconciled results; and grant no restore/workload/send authority.
- Synthetic fixtures are reserved, in-memory, deterministic, non-serializable as canonical/production input, and unreachable from application/runtime imports or discovery.
- Canonical, drift, missing/extra/reordered catalog, activation, null-filling, state-elevation, retry/resend, count/arithmetic, current-state, and sensitive-canary rejection tests pass without echo.
- Focused tests, formatting/lint/type checks, migration drift, repository quality, and graph-selected versioned verification plan pass with
uv/Make. - The final graph proves no runtime/render path. Independent tester records Playwright/screenshots
not_applicable; any runtime, provider, model, or rendered path returns the candidate to engineering as out of scope. - Engineer, independent tester, and PM reports record exact base/head, #264 identity, contract/fixture digests, changed paths, graph/plan/artifact digests, commands/counts, and no pending/skipped required component.
Required verification scenarios
- Each #264 path/API/schema/verifier/source/definition identity missing, changed, provisional, or stale; cross-contract validation fails before DTO construction.
- Contract/fixture self-digest tamper, duplicate/unknown/reordered key or catalog entry, malformed version/digest/UUID/timestamp, noncanonical bytes/Unicode, bool/float/NaN, excessive depth/items/bytes, or version reuse.
- Every missing/duplicate/extra/reordered projection state, result, reason, slot, state-count key, and idempotency input; no wildcard/default succeeds.
- Active/allowed production flag, non-null slot/bound, callable/model/query/endpoint/credential field, environment/settings/runtime override, fake production definition, or synthetic serialization/import/discovery attempt.
- Provider-accepted treated as delivered, terminal projection treated as restore-safe without proof, ambiguity treated as retryable/safe, changed request treated as exact replay, unavailable selection treated as empty, or blocked count treated as reconciled.
- Exact replay versus changed generation, #264 receipt, definition, scope, plan, batch input, result, count, current-state proof, or evaluation instant; changed inputs conflict/block and never resend.
- Missing/new/changed selectable work, all 11 observed states, callback lag, request-hash drift, incomplete state counts, count overflow/underflow, checkpoint/result-manifest drift, superseded receipt, and exact freshness boundaries.
- Email/name/username/IP/subject/domain/database/message/job/provider-ID/reversible-hash/path/query/cursor/URL/ARN/credential/token/cookie/session/backup/provider-payload/body/context/SQL/free-text/control-character canaries at every source, fixture, loader, exception, output, and log boundary; failure output never echoes the canary.
- Source/import graph proves zero Django/model/migration/URL/command/job/settings/runtime/email/provider/render reachability and no reverse import into #264.
Required verification commands
At minimum, plus the generated change-selective plan:
uv run --frozen python scripts/validate_email_outbox_reconciliation_contract.py
uv run --frozen python -m unittest scripts.tests.test_email_outbox_reconciliation_contract
make migrations-check
make verification-quality
The independent tester reruns the focused validator/tests and all graph-selected components. Playwright and screenshots are not_applicable only after independent proof of zero runtime/render impact.
Delivery and downstream order
After #264 is accepted, merged, and green, this issue follows engineer → independent tester → PM acceptance → focused commit with Closes #284 → local no-ff merge/push → on-call green.
Then PM re-grooms #49's recovery-specific scope against the exact merged contract path/version/digest and future Relay contracts. #258 may be rewritten only after #284 plus its privacy/kernel/retention prerequisites are accepted and green; it imports the eventual #49 implementation of this seam rather than defining an outbox adapter itself. #66 consumes only a later #258 fence receipt and never bypasses the email evaluator.
Production adapter versions, Relay/OpenAPI identity, execution bounds, current-state proof semantics, runtime reconciliation, provider access, worker/send release, restore activation, and any HUMAN gates remain later separately groomed work.
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
Read the named authority documents and confirm the accepted #264 identity before working on the four owned files: the two JSON contracts, scripts/validate_email_outbox_reconciliation_contract.py, and scripts/tests/test_email_outbox_reconciliation_contract.py. Run the validator and its test file first. Done means the immutable non-activating contract validates, synthetic tests cover the listed identity, state, arithmetic, drift, redaction, and non-activation cases, and no production path can load it.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100