Skip to content

Add configurable event registration questions and sponsor consent #111

Description

@alexeygrigorev

Outcome

Give event operators a reusable, versioned registration-question catalog that can be configured per event without code changes, while keeping every submitted answer and consent interpretable after later edits.

The MVP includes two deliberately different communication rules:

  1. a prebuilt per-sponsor email-share consent that is optional, unchecked, never required, and exportable only when affirmatively accepted for the named sponsor;
  2. automatic DataTalks.Club newsletter enrollment after verified event registration, with no newsletter question, only after Decision: Approve privacy ownership, retention, and minors policy #23 approves the exact notice, lawful basis, processor, retention, objection/unsubscribe, and re-enrollment rules.

Product and architecture authority

This issue explicitly amends the old generic newsletter/marketing wording in specification 05 and #46: event registration has no configurable DataTalks.Club newsletter opt-in question. Sponsor sharing remains separate affirmative consent. Public event pages never expose attendee identities, cards, names, initials, avatars, profiles, directories, or lists. #112 may publish only a non-identifying exact aggregate registration count.

Question catalog and immutable versions

The events app owns a database-backed catalog:

  • EventRegistrationQuestion has a UUID, stable key, purpose, data classification, retention class, lifecycle, and current revision.
  • EventRegistrationQuestionVersion is immutable after publication and stores exact label, help text, type, validation, option keys/labels/order, and version number.
  • EventQuestionAssignment pins one published version to one Event plus order, active state, required state where allowed, assignment revision, and activation/deactivation timestamps.
  • A submitted EventRegistrationAnswer pins registration/version, assignment and question/version identifiers, the exact rendered question/option snapshot, canonical typed answer, and submission timestamp.

Question lifecycle is draft -> published -> archived. Editing a published question creates a new version; it never mutates old wording/options. Archiving or deactivating stops future use but preserves definitions and answers. Replacing an assignment after registrations exist requires revision/If-Match, reason, impact preview, and explicit confirmation; it affects future submissions only.

Question and option text is trimmed plain text, escaped on render, never Markdown/HTML, and never synchronously fetched. Stable option keys, not labels, are stored as canonical values; the answer snapshot retains the submitted labels for historical interpretation.

Safe code-owned question types

Type Contract
short_text One plain-text value; configured maximum 1–500 characters.
long_text One plain-text value; configured maximum 1–2,000 characters.
single_select Exactly one stable option key from 2–50 published options.
multi_select Unique stable option keys from 2–50 options, with a configured maximum of 1–20 selections.
yes_no Canonical boolean; ordinary answer, never consent by itself.
acknowledgement Canonical boolean for a non-marketing statement; may be required, but cannot be relabeled or exported as consent.

Labels are 1–300 characters, help text at most 1,000, and option labels 1–200. Validation is code-defined; staff cannot supply regular expressions, HTML, executable expressions, arbitrary schemas, or client code. File upload, password/secret collection, payment, hidden tracking, conditional branching, repeating groups, and special-category/sensitive-data questions are out of MVP. A new type requires a reviewed code/schema/test change rather than arbitrary Studio configuration.

Ordinary questions may be required. Missing required answers fail before a pending registration is created; optional questions remain visibly optional. Duplicate, unknown, stale-version, disabled, or forged question/option identifiers fail safely and cannot mass-assign registration or consent fields.

Prebuilt named-sponsor email-share consent

A system-owned sponsor_email_share definition is available without custom coding. Each activation identifies exactly one reviewed sponsor record and immutable statement version. Multiple sponsors require multiple separately named unchecked choices; sponsors are never bundled.

The generated prompt names the sponsor and states that DataTalks.Club will share the registrant's email address with that sponsor for the approved purpose. The evidence pins:

  • event, registration/version, sponsor UUID and name snapshot;
  • statement/notice version and exact rendered text;
  • affirmative or non-affirmative value, submission timestamp, and source;
  • normalized email snapshot used for the approved disclosure.

This control is always optional, unchecked on every fresh form/render, and cannot be made required through Studio, API, import, query, or client payload. Silence, missing data, stale evidence, a generic agree-check, event/newsletter participation, or another sponsor's answer is not consent.

Sponsor disclosure export is scoped to one event, one named sponsor, and one statement version. It contains only affirmative, current evidence and the minimum fields: email, event identifier, sponsor name, consent timestamp, and statement version. It excludes declined/missing/revoked/stale consent, all other answers, attendee-display state, phone, employer, profile fields, and newsletter state. CSV is formula-safe; the encrypted artifact has bounded #23/#64 retention and requires explicit confirmation, reason, dedicated permission, and audit. Audit records actor/reason/scope/count/version, never email or answer text.

Automatic DataTalks.Club newsletter enrollment

There is no newsletter question in the catalog, event assignment, registration payload, answer table, or sponsor export. Operators cannot add, rename, precheck, or require one.

After #23 records an approved non-consent basis compatible with automatic enrollment, the registration page displays the exact versioned DataTalks.Club newsletter notice and unsubscribe/objection route. Successful email verification/registration confirmation atomically creates one durable NewsletterEnrollmentIntent; provider work occurs after commit and is idempotent by normalized address plus policy version. An existing subscriber is a no-op. Provider outage never rolls back registration.

A durable unsubscribe/objection/suppression state wins permanently over later event registration and cannot be reset by another event, replay, import, staff correction, or provider retry. Enrollment provenance stores the event/registration, notice and basis identifiers, requested/completed state, and safe timestamps, not a fabricated consent value. Newsletter provider credentials or raw payloads never enter the question/answer model, logs, audit, screenshots, or exports.

#23 is a hard activation dependency. Until it approves the exact basis, notice, processor, retention, minors handling, and unsubscribe/re-enrollment semantics, the system fails closed by completing event registration without creating or submitting a newsletter enrollment. If #23 concludes that affirmative consent is legally required, this automatic-enrollment requirement returns to the owner; engineers must not silently add a checkbox contrary to this issue.

Public and management flows

The question set renders on the internal event registration route /events/<slug>/register owned by #46. The base email/identity, versioned privacy acknowledgement, ordinary questions, and named-sponsor choices remain semantically distinct. There is no attendee-identity publication field or evidence. Verification, cancellation, enumeration resistance, and outbox behavior remain #46/#49 contracts.

Studio surfaces:

  • GET/POST /studio/events/questions/ — list/create reusable questions;
  • GET/POST /studio/events/questions/<uuid>/ — inspect/revise/publish/archive versions;
  • GET/POST /studio/events/<uuid>/registration-questions/ — preview/order/activate/deactivate pinned versions;
  • GET/POST /studio/events/<uuid>/sponsor-opt-ins/ — inspect aggregate consent state and confirm an affirmative-only export.

Admin API parity:

  • GET/POST /api/v1/admin/event-registration-questions;
  • GET/PATCH /api/v1/admin/event-registration-questions/<uuid> with If-Match;
  • GET/PUT /api/v1/admin/events/<uuid>/registration-questions with If-Match and Idempotency-Key;
  • GET /api/v1/admin/events/<uuid>/sponsor-opt-ins for bounded aggregate state;
  • POST /api/v1/admin/events/<uuid>/sponsor-opt-in-exports with Idempotency-Key, reason, and confirmation.

Register exact capability entries for events.registration_question.manage, events.registration_answer.view_pii, and events.sponsor_consent.export. Event operators may manage non-sensitive definitions/assignments; viewing answers or affirmative addresses requires the dedicated PII/export capability. Authorized querysets apply before lookup. Studio/admin API use the same events services, revision/idempotency rules, validation, results, redaction, and audit. Django admin remains break-glass only.

All form, answer, consent, export, Studio, and admin responses containing registration data are private/no-store/noindex and absent from sitemap/search/public serializers. Public event pages may receive only #112's exact aggregate registration total; no answer or attendee identity is public.

Migration and existing evidence

Existing event-registration privacy/marketing fields are preserved until an expand-and-contract migration classifies them. Generic legacy marketing/newsletter values are never reclassified as a named-sponsor share or DataTalks.Club newsletter consent. There is no attendee-publication purpose to infer.

#112 imports only provider/event aggregate totals and imports no legacy question definition, answer, acknowledgement, notice, consent, email, or other row-level evidence. Any future legacy-answer migration requires a separately groomed issue and cannot infer sponsor/newsletter consent or public disclosure.

Non-goals

  • No public answer/profile/directory, attendee search, answer-based personalization/ranking, CRM, arbitrary sponsor API delivery, direct sponsor email send, marketing campaign editor, or general survey builder.
  • No prechecked/bundled/required consent, inferred sponsor consent, generic marketing opt-in, or newsletter question.
  • No file upload, branching/repeating forms, arbitrary validation code, sensitive-category intake, payment, capacity/waitlist, event lifecycle rewrite, account creation, MemberProfile/Person synchronization, or course-question change.
  • No real personal-data migration before Decision: Approve privacy ownership, retention, and minors policy #23/Implement privacy inventory, consent evidence, rights, retention, and deletion replay #64 approval and no provider credential or production registration data in Git/issues/tests/screenshots.

Acceptance criteria

  • Catalog definitions, immutable published versions, event assignments, and answer snapshots enforce the exact lifecycle, limits, types, ordering, revisions, and future-only edit semantics above.
  • Public registration renders the event's active pinned versions in order, enforces required/optional rules server-side, and rejects forged/stale/disabled/unknown fields without ghost registrations.
  • The built-in sponsor choice names exactly one sponsor, is always optional/unchecked/non-required, and stores immutable statement/sponsor/email evidence only when submitted.
  • Sponsor exports contain only affirmative current opt-ins for the exact event/sponsor/version, are minimum-field/formula-safe/private/bounded, and have permission, confirmation, idempotency, and redacted audit controls.
  • No generic/provider/legacy answer, newsletter state, attendance, or registration is inferred as sponsor consent or public disclosure.
  • DataTalks.Club newsletter enrollment has no question/answer field; after Decision: Approve privacy ownership, retention, and minors policy #23 approval it is notice/basis/unsubscribe governed, durable/idempotent after verification, provider-failure tolerant, and never overrides objection/unsubscribe/suppression.
  • Question/answer/consent/export access, correction, retention, deletion/anonymization, processor propagation, and restored-backup replay integrate with Implement privacy inventory, consent evidence, rights, retention, and deletion replay #64 without leaking answer or address values.
  • Studio/admin API capability, service, validation, revision, idempotency, permission, audit, result, and OpenAPI parity covers every catalog/assignment/sponsor-export operation.
  • Focused Django, migration, security/privacy, management-parity, OpenAPI, compatibility, full event-registration, and core Playwright tests pass through repository uv/Make targets.
  • Templates remain readable/non-minified and WCAG 2.2 AA critical-flow behavior is verified at desktop/mobile.

Django, integration, and failure scenarios

  1. Every type at empty/min/max/over-limit, whitespace/Unicode/control characters, duplicate/stale/unknown option, multi-select duplicates/count, forged ID/type/required flag, disabled/archive version, and mass assignment.
  2. Draft/publish/revise/archive, assignment reorder/replace/deactivate before and after registrations, stale If-Match, duplicate Idempotency-Key, concurrent operator edits, and old-answer interpretation after wording/options change.
  3. Sponsor absent/renamed/archived, one/multiple sponsors, unchecked/missing/false/true, attempted required/prechecked/bundled payload, stale statement, revocation/correction, export empty/one/many, denied/masked role, formula payload, replay, and audit/artifact canaries.
  4. New/pending/verified/cancelled/reactivated registration, duplicate submit/back/refresh, provider/worker outage, subscriber already present, unsubscribe before/after registration, repeated events, suppression, processor error, and no Decision: Approve privacy ownership, retention, and minors policy #23-approved policy.
  5. Legacy/provider question or marketing evidence is absent from Import historical registration totals safely and show aggregate event counts #112 aggregate imports; a separately authorized future migration must preserve exact provenance and infer zero sponsor/newsletter consent.
  6. Export/deletion/retention/backup replay with shared email across events and sponsors; prove minimum disclosure and no answer/newsletter leakage.

Browser acceptance

At desktop about 1440x900 and mobile about 390x844 using synthetic identities:

  1. Configure/reuse/reorder ordinary question types in Studio, preview the exact registration form, revise after one answer, and verify the old response remains interpretable.
  2. Register with required/optional validation, preserved input, accessible error summary/focus, keyboard navigation, refresh/back/duplicate submit, and verification resume.
  3. Activate one and then two named sponsors; prove each choice is visibly optional and fresh-unchecked, cannot be required, and one affirmative answer never authorizes the other.
  4. As authorized/denied roles inspect masked state and create a confirmed sponsor export; verify it excludes negative/missing/other-answer rows and contains no unexpected PII.
  5. Verify there is no DataTalks.Club newsletter question, the approved notice/unsubscribe copy is visible when configured, provider delay does not change registration success, and prior unsubscribe is honored.
  6. Inspect desktop/mobile screenshots and browser history/network/console for responsive, non-error content with no email, answer, export URL, token, or other synthetic PII in artifacts.

Required specification and issue amendments

Implementation must update:

  • _docs/specs/05-events-registration-email.md — question/version/answer model, named-sponsor consent/export, and automatic DTC-newsletter exception to old generic marketing wording;
  • _docs/specs/06-studio-and-admin-api.md — exact routes, capabilities, masking, parity, and audit;
  • _docs/specs/07-security-privacy-operations.md — purposes/bases, answer/consent PII, unsubscribe, export/retention/deletion rules;
  • _docs/specs/09-migration-rollout-roadmap.md — expand-and-contract and Luma evidence precedence;
  • _docs/specs/10-verification-strategy.md — form/version/consent/newsletter/browser/security matrices;
  • _docs/specs/open-decisions.md — record the owner-selected DTC newsletter behavior as conditional on Decision: Approve privacy ownership, retention, and minors policy #23 approval.

#46 and #47 are amended with cross-references: no public attendee identity, card, directory, list, search, or export is permitted; #112 may expose only an exact aggregate registered count. Sponsor consent is separate, and no generic newsletter question remains.

Dependencies and delivery

Source implementation and synthetic tests may be prepared before #23, but real enrollment/provider activation and real registration data remain disabled. No production or protected export data may enter the repository or issue evidence.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Must-have or release-blockingaccessibilityArea: accessibilityadminArea: adminemailArea: emailenhancementNew feature or requesteventsArea: eventsfrontendArea: frontendintegrationArea: integrationoperationsArea: operationssecurityArea: securitytestingArea: testing

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions