DataTalksClub / DataTalksClub/website

Remove request-time certificate notification fanout

Open
#233 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug courses email integration operations P1 testing
Dominant language
Python
Stars
0
Forks
0
PR merge metrics
No merged PRs in 30d

Description

Parent integration epic: #50. Upstream adoption coordination: #149.

PM disposition

GROOMED / P1 / DEPENDENCY-BLOCKED. Do not dispatch engineering yet.

Fresh CMP evidence is unchanged: DataTalksClub/course-management-platform main is
8a4d05221051748e3d95bf446c0d1cfa46dc111a, 30 commits ahead of the website pin
98a235283904b4ef9ad29e196298540756cf1bcc. The applicable source packet remains
da2c7ae6e11acc860fd98c6832e674d3f483785f (Split certificate upload and notification into separate endpoints). The later CMP certificate-normalization packet
77906c7acdf9a68adb0180e0dd70443d9aa3220f is owned separately by #234. This issue does not move
the global CMP pin.

The source diagnosis applies: the copied compatibility bulk-certificate path schedules one
transaction.on_commit Datamailer call per first-time certificate. With Datamailer configured, a
large request can perform unbounded synchronous network fanout after the database commit and exceed
the request-worker timeout.

The source remedy is not target-compatible. CMP makes upload return a notify flag and adds a
caller-driven POST .../certificates/notify endpoint that still performs one synchronous
Datamailer call. The website must not expose that endpoint, ask a caller to orchestrate delivery,
call Datamailer/SES/Relay from a request or on_commit callback, or treat a successful upload as
proof that notification happened.

Normative authority

  • _docs/specs/04-courses-and-cohorts.md — cohort-owned certificate lifecycle, preserved
    compatibility behavior, shared services, and send-disabled Datamailer history.
  • _docs/specs/05-events-registration-email.md — the certificate purpose catalog, immutable Relay
    routing, atomic business state + one logical EmailDelivery + one durable job, and leased
    after-commit Relay submission.
  • _docs/specs/06-studio-and-admin-api.md — certificate operations use shared application services
    with authorization, idempotency, audit, and adapter parity.
  • _docs/specs/07-security-privacy-operations.md — redaction, bounded retries, no direct provider or
    Datamailer fallback, and safe failure behavior.
  • _docs/adoption/course-platform/upstream-sync.md and
    _docs/adoption/course-platform/source-pin.json — selective review only; no automatic sync or pin
    movement.

Target-native upload/delivery split

  1. The compatibility upload adapter calls the accepted #58 certificate application service. #58
    owns certificate issue/update/revoke/reissue state, version identity, first-issue semantics,
    cohort isolation, and the immutable message-trigger-ready scalar contract.
  2. The bounded certificate-purpose adapter contributed to #50 consumes that accepted trigger and
    calls #49's accepted atomic intent/job interface inside the same owning database transaction.
    The certificate transition, exactly one logical delivery intent, and exactly one durable job
    commit together or all roll back.
  3. The request returns the existing bounded per-item compatibility result after persistence. It
    neither sends nor exposes a second notification command. A leased/fenced worker calls Relay only
    after commit using the immutable template/version and stable certificate-purpose idempotency
    inputs.
  4. Relay failure leaves committed certificate state and durable work observable. Exact replay
    converges; an uncertain acknowledgement becomes ambiguous and is never automatically resent.

This issue is the bounded compatibility/certificate-routing slice under #50. It must consume #58
and #49; it must not recreate their domain, job, transport, preference, or status rules. Its focused
commit closes #233 and references #50. #50 remains open for other purposes, Datamailer inventory and
migration, operational safeguards, HUMAN canary, and retirement.

Scope after dependencies are accepted

  • Replace the copied bulk endpoint's injected Datamailer callback and per-enrollment on_commit
    fanout with the accepted #58 + #49 application-service composition.
  • Register only the certificate issue/revoke/reissue purpose mapping owned by this bounded #50
    slice, using #48's accepted immutable template/version boundary and the recipient-preference rule
    supplied by the accepted delivery contract.
  • Preserve the compatibility route, authentication, input order/correlation, validation, and
    per-item success/error shape unless an accepted #58 contract explicitly versions a change.
  • Make exact replay, duplicate batch rows, concurrent requests, replacement, revoke, and reissue
    follow #58's certificate version semantics and #49's stable idempotency interface.
  • Remove runtime reachability of the certificate-specific Datamailer callback from this request
    path; retain legacy artifacts only as send-disabled history/characterization input owned by #50.
  • Record source SHA da2c7ae6e11acc860fd98c6832e674d3f483785f and the selective target adaptation in the adoption
    overlay without moving the global pin.

Direct dependencies and interfaces

  • #58: accepted certificate application-service symbol, transaction boundary, certificate
    version/transition identity, first-issue/revoke/reissue semantics, authorization/audit contract,
    and message-trigger-ready scalar payload.
  • #48: accepted immutable Relay certificate template key/version and sender/routing consumer
    boundary. #233 does not author or render a template.
  • #49 ordinary delivery: accepted callable service/DTO for atomically creating or resolving one
    logical EmailDelivery plus durable job, stable idempotency/request-hash inputs, preference
    suppression result, redacted projection, ambiguity, and safe retry/reconciliation behavior.

Relay #1 is transitive through #48. Relay #2/#3 and accepted website #31/#32/#33 foundations are
transitive through #49. #22, #23, and #124 are accepted policy/specification inputs, not open
dependencies. #284 governs restore-generation reconciliation and is not a prerequisite for this
ordinary certificate-delivery slice; it remains required before #49/#50 can claim complete recovery
behavior. #234 coordinates normalized collision-safe lookup and must be replayed in integration
order if its accepted files overlap, but it supplies no model or interface required by this issue
and is not a hard dependency.

Before dispatch, PM must freeze the exact accepted #58/#48/#49 merge SHAs, service symbols and DTO
schemas, certificate-purpose route/template/version, idempotency/version tuple, preference result,
source pin, migration leaves, and overlapping #234 disposition. Until then this issue is blocked,
not raw intake.

No independent source-only runtime lane

There is no safe pre-dependency runtime subset:

  • porting upload-only behavior would commit certificates while silently losing durable notification
    work;
  • porting CMP's /certificates/notify endpoint would preserve forbidden request-time Datamailer
    execution and caller-managed retries;
  • returning a notify flag is not an intent, job, recovery record, or delivery receipt;
  • adding a temporary table/outbox would invent the #49 interface and create migration debt; and
  • disabling only this sender without a durable replacement would change certificate communication
    semantics without an accepted product failure policy.

The deterministic static Datamailer source inventory is already owned by #290 under #50. It may
proceed independently but is not #233 implementation or acceptance evidence.

Non-goals

  • No synchronous /certificates/notify port, caller-side notification loop, direct Relay,
    Datamailer, SES, provider, or on_commit network call.
  • No certificate eligibility, scoring, lifecycle, version, revoke/reissue, account resolution, or
    template-authoring redesign; consume #58, #234 where integrated, and #48.
  • No second delivery/outbox/status model, local renderer, manual resend UI, production credential,
    live recipient, canary, deployment, source-pin advance, or broad #50 retirement work.
  • No claim that source code, tests, or a Relay deployment grants protected/provider authority.

Acceptance criteria after unblocking

  • The compatibility bulk-certificate request and every transaction/model/on_commit callback
    it invokes make zero Relay, Datamailer, SES, or other notification network calls.
  • Every accepted #58 certificate transition that requires a message atomically commits exactly
    one #49 logical intent and durable job; rollback creates neither certificate state nor delivery
    work.
  • Exact replay, duplicate rows, concurrent requests, replacements, revoke, and reissue converge
    according to the accepted certificate-version and delivery-idempotency contracts without duplicate
    first-issue work.
  • Mixed valid/invalid input preserves stable indexes and safe per-item results; valid state and
    corresponding delivery work commit consistently, while database failure rolls the transaction
    back.
  • The compatibility API exposes no notification endpoint or caller-managed notify workflow.
    Only a leased/fenced worker contacts Relay after commit.
  • Preference suppression, Relay outage, timeout/response loss, ambiguity, reconciliation, and
    job failure preserve certificate state and expose only the accepted redacted status/reason.
  • Certificate-specific Datamailer submit/requeue reachability is absent from the request path;
    no SES/Datamailer fallback or second sender can activate.
  • Existing upload/export/learner compatibility and #234's accepted lookup behavior remain
    passing. Adoption evidence names source SHA
    da2c7ae6e11acc860fd98c6832e674d3f483785f and leaves the global pin unchanged.
  • Focused Django/API/job tests cover a large synthetic cohort, first issue/replacement/revoke/
    reissue, replay/concurrency/rollback, mixed batches, preference suppression, Relay outage/
    ambiguity, and outbound-network denial. Browser screenshots are not_applicable only if the
    independently recomputed change graph proves no rendered impact.

Lifecycle

After #58, #48, and ordinary #49 are accepted and integrated, PM refreshes the frozen interfaces and
dispatches one isolated current-main engineer lane. Engineering remains uncommitted; a separate
tester recomputes the plan and verifies every criterion; PM accepts only a terminal tester PASS;
then the focused commit uses Closes #233 and Refs #50, followed by local --no-ff merge, push,
and on-call observation. No pull request and no live email/provider access.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Before implementation, read the listed _docs/specs/04-courses-and-cohorts.md through 07-security-privacy-operations.md and wait for frozen #58, #48, and #49 interfaces. Then inspect the compatibility bulk-certificate endpoint and focused Django/API/job tests. Done means the request path has no notification network calls and the acceptance tests pass after atomic certificate and delivery-job behavior is integrated.

Written by the indexing model from the issue text.

Assessment

Tech stack
django, python
Domain
api, backend, databases, testing-qa
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
18/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.