You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Safely derive and publish aggregate-only historical registration totals from protected Luma and Eventbrite exports.
For each canonical event/provider/source revision, persist a reviewed count and reconciliation metadata—not legacy attendee rows. The public event detail may render only the exact integer copy N registered. It never renders attendee cards, names, initials, avatars, profiles, attendee links, or any other attendee identity. There is no public attendee directory or identity exception.
Current native DataTalks.Club registrations remain protected records under #46/#47. The public query combines eligible native registrations with historical aggregate contributions only when coverage and replacement rules prove they do not overlap.
This owner decision supersedes the earlier Luma row-import and attendee-identity contract. #112 imports no legacy registration row, answer, consent, or attendee-identity evidence and no longer depends on #111 or #23. #111 remains independently authoritative for new DTC registration questions, named-sponsor consent, and the separately gated automatic DTC newsletter behavior.
Protected source facts
Only aggregate/schema evidence may appear in source control, tests, logs, screenshots, APIs, or issue reports. No attendee row, filename/event name, email, person, answer, raw attendee ID, local path, or secret may be copied into evidence.
Luma snapshot
The protected snapshot contains:
159 event JSON documents paired one-to-one with 159 CSVs;
schema version 1, 50,505 unique provider event/guest pairs, and no duplicate normalized event/email pairs;
50,456 approved and 49 declined rows; 157 events have rows and two are empty;
stable event/guest identifiers and timestamps;
no attendee-visibility, sponsor-share, newsletter, marketing, privacy-notice, or consent field.
Only 64 of the 159 source events exactly match the complete provider URL recorded by the pinned canonical event projection. The other 95 require explicit reviewed mapping or exclusion. Title, slug, path token, date, or fuzzy similarity may rank masked review candidates only; none may activate a total.
Eventbrite snapshot
The protected archive has SHA-256 5cc493c7e9a142d09f5a524d28df486f4fa33ce832210ea0d325025b939744df and contains 210 entries: 209 CSVs and one XLSX. Its verified expansion ratio is 3.80x, with no hidden, traversal, or symlink entries.
The 209 CSVs contain 24,001 parsed attendee rows across 209 unique numeric provider event IDs. Every current row has categorical status Attending, and the aggregate audit found no duplicate protected attendee keys.
9 source IDs require explicit reviewed mapping or exclusion through that source-key bridge;
27 Eventbrite IDs present in pinned legacy _data/events.yaml have no CSV in this archive and are recorded as source_missing, never inferred as zero.
Three exact ordered CSV schemas are allowlisted after UTF-8 decoding with an optional BOM and RFC-4180 parsing. The header fingerprint is SHA-256 over the exact ordered header strings joined by byte 0x1f:
Each accepted schema must contain the exact code-owned mappings for Order #, Order Date, Attendee #, and Attendee Status; the numeric event ID comes only from the validated archive-entry contract and is cross-checked against its mapping. Reordered, missing, duplicate, or additional headers create a new unsupported schema and quarantine that entry. The XLSX is never opened or implicitly converted; it is recorded as unsupported_xlsx and quarantined. An unsupported CSV is likewise quarantined rather than partially parsed.
Name, email, ticket text, newsletter/sponsor questions, and every other unused column are ignored after schema validation and never persisted. The source contains aggregate newsletter answers (12,127 affirmative, 9,103 negative, 2,771 blank/absent); #112 stores none of those answers and causes no newsletter, sponsor, consent, or email side effect. A future PII migration would require its own groomed issue and must preserve exact negative/objection evidence.
Canonical mapping and replaceable aggregate overlay
#112 uses the stable checked canonical event source identity and canonical slug already accepted in #105. It does not wait for #45's full database Event lifecycle.
The events app owns these provider-neutral records:
HistoricalRegistrationSourceRun: UUID, provider (luma or eventbrite), adapter/schema version, whole-source checksum, safe manifest/row/status totals, mapping-set revision, policy version, state, revision, actor/reason, and timestamps. It stores no local path or row data.
HistoricalEventMapping: provider plus exact external event identifier, Ship real public content on the final editorial route map #105 canonical source identity, canonical slug snapshot, mapping state, mapping revision, reviewer/reason, and timestamps. Provider event IDs are protected operational identifiers, never public output or issue/log evidence.
One active aggregate pointer per canonical event/provider/coverage slot. Activation and replacement use compare-and-swap revision guards and a uniqueness constraint; history is never overwritten.
Mapping state is exactly review_required, mapped, excluded, or source_missing. Aggregate state is exactly staged, validated, active, superseded, quarantined, or rolled_back. Only an authorized, reviewed mapped record with a validated aggregate may become active.
An exact provider identifier/full-provider-URL match may prepopulate a mapping proposal but still records the mapping-set revision used for activation. Title, event name, filename, slug similarity, date proximity, or fuzzy matching never activates or silently excludes a source. An exclusion requires reviewer, bounded reason code, free-text reason kept out of public evidence, and revision.
When #45 introduces database-owned Event UUIDs, an expand-and-contract migration resolves each accepted #105 source identity/slug to exactly one Event, attaches the aggregate records, reconciles counts, and only then switches the query. Missing, duplicate, or changed identity blocks cutover; it never guesses.
Aggregate derivation and status policy
The staging adapter reads only an explicitly supplied protected source reference. It rejects hidden entries, traversal, symlinks, duplicate entries/IDs, malformed encodings/CSV/JSON, zip bombs or configured size/count limits, checksum drift, mismatched Luma JSON/CSV pairs, unsupported schema/status, and unsafe archive structure before activation.
It streams rows, validates the exact provider schema, and deduplicates in memory by the provider event/registration identity. The temporary set is discarded after derivation; no row, email, name, attendee identifier, attendee digest, answer, timestamp, or provider payload is written to the application database or report.
The versioned MVP status policy is:
Luma approved: eligible;
Eventbrite Attending: eligible;
declined, cancelled, rejected, duplicate, quarantined, malformed, unknown, or excluded records: not eligible;
no status is interpreted as attendance/check-in evidence.
Unknown statuses quarantine that provider-event aggregate. Safe reports contain only source checksum, provider, schema/policy/mapping revisions, event-count totals, row-count totals, state counts, and bounded reason codes.
Dry-run and replay of the same source checksum/schema/policy/mapping revision are deterministic no-ops. A changed source creates a new immutable staged revision; it never mutates the active total silently. Activation is atomic for the reviewed mapping set, keeps the prior active revision available for rollback, and publishes no partial candidate.
No-double-count query and replacement policy
The events query service is the only source of the public total. It returns an integer plus a total revision/completeness state, never provider breakdown or attendee data.
Each count contribution occupies an explicit canonical-event coverage slot. Exactly one source may be active in a slot:
one aggregate revision; or
a future reviewed row-level replacement projection.
Replacing an aggregate with later row-level migration atomically marks the aggregate superseded and activates the replacement in the same transaction. The query can therefore count the aggregate or its replacement, never both. Rollback restores the prior pointer and total revision without deleting history.
Multiple historical providers for one event are not additive by default. A reviewer must classify their coverage as:
additive_disjoint, with evidence of non-overlapping provider/time coverage;
replacement, naming the contribution that wins; or
exclude.
Unknown or overlapping coverage quarantines the candidate and leaves the last accepted public total unchanged. Aggregate-only data cannot deduplicate people across providers, so it must never claim that title/date/provider similarity proves disjointness.
Native DTC rows use a separate coverage slot beginning at the reviewed cutover boundary. Only active confirmed, attended, and no_show native registrations count; pending, cancelled, expired, rejected, duplicate/quarantined, and deleted/anonymized records do not. A missing/uncertain boundary blocks aggregation rather than double-counting historical and native sources.
A canonical event with complete accepted coverage renders exactly N registered, using an unrounded non-negative integer. registered never becomes attended. A source_missing, review-required, quarantined, or incomplete coverage state is never presented as zero; the count is omitted with no attendee fallback until reconciliation succeeds.
Public event detail, cache, and invalidation
The count appears only on the canonical detail /events/<slug> owned by #105/#45. The hub does not gain a count in this issue. No public response contains provider totals, source state, mappings, filenames, external event IDs, attendee fields, initials, avatars, profiles, a list/search/export endpoint, or custom answers.
Protected staging, Studio, admin API, native registration, and management routes are private/no-store/noindex and zero-TTL. Development remains noindex/nofollow and robots-disallowed.
Aggregate activation, replacement, rollback, mapping invalidation, native confirmation/reactivation/cancellation, deletion/anonymization, or coverage-state change increments one public total revision and creates one durable after-commit invalidation intent for /events/<slug>. Duplicate work coalesces by event plus total revision. Until #109's public-cache isolation/invalidation contract is proven, event details carrying a count remain zero-TTL/no-store. A failed invalidation retries/alerts and cannot publish a partial candidate or an identity.
Studio and admin API parity
Studio:
GET /studio/events/historical-registration-totals/ — provider-neutral runs and safe aggregate state;
GET /api/v1/admin/historical-registration-imports/<uuid>;
POST /api/v1/admin/historical-registration-imports/<uuid>/{dry-run,validate,activate,cancel,rollback} with Idempotency-Key;
GET/POST /api/v1/admin/historical-event-mappings and PATCH /api/v1/admin/historical-event-mappings/<uuid> with If-Match;
GET /api/v1/admin/events/<canonical-key>/registration-total.
The local protected-source adapter registers a checksum/reference; Studio/API never accept or return an arbitrary server path or upload the archive. All adapters call the same events application services and enforce the same state transitions, mapping/coverage validation, revisions, idempotency, authorization, results, and rollback.
Register events.historical_registration_mapping.manage and events.historical_registration_import.manage. Exact provider event identifiers are visible only where necessary to the mapping capability and are masked from ordinary run views. Every map/exclude/validate/activate/replace/rollback action records actor, reason, revisions, source checksum, safe aggregate counts, prior/new state, and outcome—never attendee data, raw paths, filenames, row identifiers, or answers. OpenAPI and tests use synthetic values only.
Failure and rollback
Validation or mapping failure activates nothing. A process crash/replay resumes or deterministically returns the stored result without duplicate revisions. Changed checksum, unsupported schema/status, missing source, ambiguous mapping, overlapping provider/native coverage, count mismatch, stale revision, or concurrent activation quarantines the candidate and preserves the last accepted public total.
Rollback is a confirmed, reasoned, idempotent pointer transition to the prior accepted aggregate set, increments the public total revision, and invalidates the event detail. It never deletes native registrations, creates legacy rows, rewrites another provider, sends mail, or exposes protected data.
Source retention/deletion remains outside the aggregate database and follows #64's approved protected-source handling. The application retains only minimized non-PII aggregate/provenance/audit data. No failure triggers verification/confirmation/newsletter/transactional email, sponsor sharing, consent creation, public identity, or provider write-back.
Non-goals
No legacy EventRegistration, answer, consent, email, name, person, source guest ID/digest, attendee timestamp, or other row-level PII storage/import.
No attendee card, name/initial, avatar/photo, attendee-identity evidence, public attendee identity, directory, search, list, or export.
No fuzzy/automatic mapping, silent Event creation/publication, inferred zero for missing archives, overlapping aggregate sum, attendance inference, or unreviewed replacement.
No Luma/Eventbrite API, webhook, write-back, provider credential management, ticket/payment/QR/wallet import, or attendance migration.
No newsletter enrollment/question, sponsor share/export, transactional/verification email, privacy acknowledgement, or consent side effect from historical sources.
Provider-neutral models enforce immutable source/aggregate revisions, exact mapping/aggregate states, source checksum/schema/status/mapping policy, one active contribution per coverage slot, and revision-guarded activation/rollback.
The Luma adapter reconciles 159 paired sources and exact 50,505/50,456/49 totals; only 64 exact full-URL matches are proposals and all 95 others are explicitly reviewed/map-or-exclude without guessed activation.
The Eventbrite adapter verifies the exact archive checksum/210-entry/209-CSV/one-XLSX/24,001-row/209-ID facts, accepts only the three pinned CSV schema fingerprints, quarantines XLSX/unsupported schemas, reconciles 200 exact legacy-source-ID bridge matches, 9 reviewed map-or-exclude cases, and 27 source-missing legacy IDs without claiming that Ship real public content on the final editorial route map #105 stores Eventbrite IDs.
Derivation persists only counts/provenance/mapping/audit metadata and proves zero legacy row, PII, attendee digest, answer, notice, consent, email, newsletter, sponsor, or provider side effect.
Exact status handling counts only Luma approved and Eventbrite Attending; declined/cancelled/rejected/unknown/quarantined/excluded values never contribute or imply attendance.
Replay/checksum drift/crash/concurrency/mapping revision produces deterministic no-op or quarantine behavior and never partially activates a candidate.
Cross-provider, native-cutover, and future row-replacement coverage is explicitly additive/replacement/excluded; ambiguous overlap fails closed and aggregate replacement can never be counted with the replacing rows.
A complete event detail renders only exact N registered; incomplete/review/source-missing state is not shown as zero, and no public route/serializer/cache/search/sitemap/structured data exposes attendee identity or provider breakdown.
Total-changing mutations increment the representation revision and create/coalesce the exact durable after-commit invalidation; count pages remain zero-TTL until Cache anonymous public traffic safely at CloudFront #109 proof.
Studio/admin API capability, shared-service, permission/masking, revision/idempotency, audit/redaction, result, OpenAPI, and rollback parity covers every mapping/import/total-preview operation.
Focused Django/model/migration/import/security/privacy/parity/OpenAPI/cache/SEO/full-event and core Playwright tests pass through repository uv/Make targets.
Templates are readable/non-minified, WCAG 2.2 AA critical-flow behavior is verified at desktop/mobile, and screenshots contain no attendee/source PII.
[HUMAN] An authorized operator verifies protected-source checksums, all exact/review/exclude/source-missing mappings, status/schema/coverage policy, dry-run/replay/activation/rollback, public totals, and cache invalidation in development with aggregate-only redacted evidence and zero stored/reported attendee rows.
Automated and failure scenarios
Both providers: exact/missing/changed checksum, duplicate/hidden/traversal/symlink entry, decompression/size bound, malformed encoding/CSV/JSON, missing/extra/reordered header, unsupported XLSX/schema/status, duplicate event/attendee key, and row/count mismatch.
Exact/review-required/mapped/excluded/source-missing mappings; changed canonical source identity/slug; stale mapping revision; two targets; one target with two providers; title/date/slug/fuzzy candidate; denied role.
Historical-only, native-only, disjoint historical+native, overlapping/unknown cutover, disjoint providers, provider replacement, aggregate-to-row replacement, failed replacement, and rollback; assert every slot is counted exactly once.
Public total at zero/one/many/incomplete/source-missing/quarantined states; stale cache, invalidation retry/terminal failure, and prior-active fallback.
Audit/log/metric/error/API/OpenAPI/export/screenshot canaries prove no local path, filename/event name, raw row, attendee ID/digest, email/name, answer, provider payload, token, or secret leakage and no mail/newsletter/sponsor/provider job.
Browser acceptance
At desktop about 1440×900 and mobile about 390×844 using synthetic fixtures only:
Navigate /events to /events/<slug>; verify exact 0 registered, 1 registered, and plural-count rendering where coverage is complete, plus an omitted count for incomplete/quarantined evidence.
Verify the detail has no attendee card/name/initial/avatar/profile/list/search/export/provider-total UI in HTML, accessibility tree, structured data, network responses, or cached responses.
In Studio review exact/masked mapping proposals, map/exclude a provider event, validate and activate a staged aggregate, inspect safe reconciliation, replay, replace, and roll back; exercise denied, stale, conflicting-coverage, unsupported-schema, and source-missing states.
Warm the public detail before and after a synthetic total change; verify revision/invalidation or required zero-TTL behavior without exposing provider or attendee data.
Inspect desktop/mobile screenshots plus browser history/network/console for expected pages rather than debug/404 states and for absence of protected values.
Required specification and issue amendments
Implementation updates specifications 02, 05, 06, 07, 09, and 10 for the aggregate overlay, exact public count, no-public-identity boundary, provider-neutral management, protected-source minimization, replacement/deduplication, invalidation, rollback, and verification.
#45, #46, and #47 are amended now: public event pages may expose only #112's exact aggregate registered total and never an attendee identity/list. #111 is amended to remove the abandoned attendee-identity exception and row-import dependency.
Because protected-source reconciliation requires an authorized operator, implementation commits use Refs #112, retain human, and leave the issue open until the HUMAN criterion passes. No protected export, mapping values, attendee data, provider credential, runtime state, or screenshot is committed.
Outcome
Safely derive and publish aggregate-only historical registration totals from protected Luma and Eventbrite exports.
For each canonical event/provider/source revision, persist a reviewed count and reconciliation metadata—not legacy attendee rows. The public event detail may render only the exact integer copy
N registered. It never renders attendee cards, names, initials, avatars, profiles, attendee links, or any other attendee identity. There is no public attendee directory or identity exception.Current native DataTalks.Club registrations remain protected records under #46/#47. The public query combines eligible native registrations with historical aggregate contributions only when coverage and replacement rules prove they do not overlap.
Product and architecture authority
This owner decision supersedes the earlier Luma row-import and attendee-identity contract. #112 imports no legacy registration row, answer, consent, or attendee-identity evidence and no longer depends on #111 or #23. #111 remains independently authoritative for new DTC registration questions, named-sponsor consent, and the separately gated automatic DTC newsletter behavior.
Protected source facts
Only aggregate/schema evidence may appear in source control, tests, logs, screenshots, APIs, or issue reports. No attendee row, filename/event name, email, person, answer, raw attendee ID, local path, or secret may be copied into evidence.
Luma snapshot
The protected snapshot contains:
approvedand 49declinedrows; 157 events have rows and two are empty;Only 64 of the 159 source events exactly match the complete provider URL recorded by the pinned canonical event projection. The other 95 require explicit reviewed mapping or exclusion. Title, slug, path token, date, or fuzzy similarity may rank masked review candidates only; none may activate a total.
Eventbrite snapshot
The protected archive has SHA-256
5cc493c7e9a142d09f5a524d28df486f4fa33ce832210ea0d325025b939744dfand contains 210 entries: 209 CSVs and one XLSX. Its verified expansion ratio is 3.80x, with no hidden, traversal, or symlink entries.The 209 CSVs contain 24,001 parsed attendee rows across 209 unique numeric provider event IDs. Every current row has categorical status
Attending, and the aggregate audit found no duplicate protected attendee keys.Mapping evidence is exact:
_data/events.yamlsource records; those records bridge to Ship real public content on the final editorial route map #105 canonical event identities by stable checked source key because the current Ship real public content on the final editorial route map #105 projection stores no literal Eventbrite URL/ID value;_data/events.yamlhave no CSV in this archive and are recorded assource_missing, never inferred as zero.Three exact ordered CSV schemas are allowlisted after UTF-8 decoding with an optional BOM and RFC-4180 parsing. The header fingerprint is SHA-256 over the exact ordered header strings joined by byte
0x1f:eventbrite_csv_v1333061583991588f9b6bc78c9873feb7ddab8711687ee999da2135a4cbef0c7eeventbrite_csv_v26f7f37db55176240fa695289cf13c8bcbaf86970f00b0ed18c4f2a1a6ee4e9aeeventbrite_csv_v3c3a799fcbcee38d3e1733fc0cd317e84236f5d17241513c1a76b3646a19ea0b8Each accepted schema must contain the exact code-owned mappings for
Order #,Order Date,Attendee #, andAttendee Status; the numeric event ID comes only from the validated archive-entry contract and is cross-checked against its mapping. Reordered, missing, duplicate, or additional headers create a new unsupported schema and quarantine that entry. The XLSX is never opened or implicitly converted; it is recorded asunsupported_xlsxand quarantined. An unsupported CSV is likewise quarantined rather than partially parsed.Name, email, ticket text, newsletter/sponsor questions, and every other unused column are ignored after schema validation and never persisted. The source contains aggregate newsletter answers (12,127 affirmative, 9,103 negative, 2,771 blank/absent); #112 stores none of those answers and causes no newsletter, sponsor, consent, or email side effect. A future PII migration would require its own groomed issue and must preserve exact negative/objection evidence.
Canonical mapping and replaceable aggregate overlay
#112 uses the stable checked canonical event source identity and canonical slug already accepted in #105. It does not wait for #45's full database Event lifecycle.
The
eventsapp owns these provider-neutral records:HistoricalRegistrationSourceRun: UUID, provider (lumaoreventbrite), adapter/schema version, whole-source checksum, safe manifest/row/status totals, mapping-set revision, policy version, state, revision, actor/reason, and timestamps. It stores no local path or row data.HistoricalEventMapping: provider plus exact external event identifier, Ship real public content on the final editorial route map #105 canonical source identity, canonical slug snapshot, mapping state, mapping revision, reviewer/reason, and timestamps. Provider event IDs are protected operational identifiers, never public output or issue/log evidence.HistoricalRegistrationAggregateRevision: immutable source-run/mapping references, eligible count, excluded/quarantined safe counts, coverage boundary, status-policy version, combination policy, checksum, state, and revision.Mapping state is exactly
review_required,mapped,excluded, orsource_missing. Aggregate state is exactlystaged,validated,active,superseded,quarantined, orrolled_back. Only an authorized, reviewedmappedrecord with avalidatedaggregate may becomeactive.An exact provider identifier/full-provider-URL match may prepopulate a mapping proposal but still records the mapping-set revision used for activation. Title, event name, filename, slug similarity, date proximity, or fuzzy matching never activates or silently excludes a source. An exclusion requires reviewer, bounded reason code, free-text reason kept out of public evidence, and revision.
When #45 introduces database-owned Event UUIDs, an expand-and-contract migration resolves each accepted #105 source identity/slug to exactly one Event, attaches the aggregate records, reconciles counts, and only then switches the query. Missing, duplicate, or changed identity blocks cutover; it never guesses.
Aggregate derivation and status policy
The staging adapter reads only an explicitly supplied protected source reference. It rejects hidden entries, traversal, symlinks, duplicate entries/IDs, malformed encodings/CSV/JSON, zip bombs or configured size/count limits, checksum drift, mismatched Luma JSON/CSV pairs, unsupported schema/status, and unsafe archive structure before activation.
It streams rows, validates the exact provider schema, and deduplicates in memory by the provider event/registration identity. The temporary set is discarded after derivation; no row, email, name, attendee identifier, attendee digest, answer, timestamp, or provider payload is written to the application database or report.
The versioned MVP status policy is:
approved: eligible;Attending: eligible;Unknown statuses quarantine that provider-event aggregate. Safe reports contain only source checksum, provider, schema/policy/mapping revisions, event-count totals, row-count totals, state counts, and bounded reason codes.
Dry-run and replay of the same source checksum/schema/policy/mapping revision are deterministic no-ops. A changed source creates a new immutable staged revision; it never mutates the active total silently. Activation is atomic for the reviewed mapping set, keeps the prior active revision available for rollback, and publishes no partial candidate.
No-double-count query and replacement policy
The events query service is the only source of the public total. It returns an integer plus a total revision/completeness state, never provider breakdown or attendee data.
Each count contribution occupies an explicit canonical-event coverage slot. Exactly one source may be active in a slot:
Replacing an aggregate with later row-level migration atomically marks the aggregate
supersededand activates the replacement in the same transaction. The query can therefore count the aggregate or its replacement, never both. Rollback restores the prior pointer and total revision without deleting history.Multiple historical providers for one event are not additive by default. A reviewer must classify their coverage as:
additive_disjoint, with evidence of non-overlapping provider/time coverage;replacement, naming the contribution that wins; orexclude.Unknown or overlapping coverage quarantines the candidate and leaves the last accepted public total unchanged. Aggregate-only data cannot deduplicate people across providers, so it must never claim that title/date/provider similarity proves disjointness.
Native DTC rows use a separate coverage slot beginning at the reviewed cutover boundary. Only active
confirmed,attended, andno_shownative registrations count; pending, cancelled, expired, rejected, duplicate/quarantined, and deleted/anonymized records do not. A missing/uncertain boundary blocks aggregation rather than double-counting historical and native sources.A canonical event with complete accepted coverage renders exactly
N registered, using an unrounded non-negative integer.registerednever becomesattended. Asource_missing, review-required, quarantined, or incomplete coverage state is never presented as zero; the count is omitted with no attendee fallback until reconciliation succeeds.Public event detail, cache, and invalidation
The count appears only on the canonical detail
/events/<slug>owned by #105/#45. The hub does not gain a count in this issue. No public response contains provider totals, source state, mappings, filenames, external event IDs, attendee fields, initials, avatars, profiles, a list/search/export endpoint, or custom answers.Protected staging, Studio, admin API, native registration, and management routes are private/no-store/noindex and zero-TTL. Development remains noindex/nofollow and robots-disallowed.
Aggregate activation, replacement, rollback, mapping invalidation, native confirmation/reactivation/cancellation, deletion/anonymization, or coverage-state change increments one public total revision and creates one durable after-commit invalidation intent for
/events/<slug>. Duplicate work coalesces by event plus total revision. Until #109's public-cache isolation/invalidation contract is proven, event details carrying a count remain zero-TTL/no-store. A failed invalidation retries/alerts and cannot publish a partial candidate or an identity.Studio and admin API parity
Studio:
GET /studio/events/historical-registration-totals/— provider-neutral runs and safe aggregate state;GET/POST /studio/events/historical-registration-totals/mappings/— review/revise/map/exclude exact provider-event mappings;GET /studio/events/historical-registration-totals/<uuid>/— safe source/mapping/reconciliation preview and results;POSTactions on the detail for dry-run, validate, activate, cancel, and rollback;GET /studio/events/<canonical-key>/registration-total/— protected contribution/completeness preview without attendee rows.Admin API parity:
GET/POST /api/v1/admin/historical-registration-imports;GET /api/v1/admin/historical-registration-imports/<uuid>;POST /api/v1/admin/historical-registration-imports/<uuid>/{dry-run,validate,activate,cancel,rollback}withIdempotency-Key;GET/POST /api/v1/admin/historical-event-mappingsandPATCH /api/v1/admin/historical-event-mappings/<uuid>withIf-Match;GET /api/v1/admin/events/<canonical-key>/registration-total.The local protected-source adapter registers a checksum/reference; Studio/API never accept or return an arbitrary server path or upload the archive. All adapters call the same events application services and enforce the same state transitions, mapping/coverage validation, revisions, idempotency, authorization, results, and rollback.
Register
events.historical_registration_mapping.manageandevents.historical_registration_import.manage. Exact provider event identifiers are visible only where necessary to the mapping capability and are masked from ordinary run views. Every map/exclude/validate/activate/replace/rollback action records actor, reason, revisions, source checksum, safe aggregate counts, prior/new state, and outcome—never attendee data, raw paths, filenames, row identifiers, or answers. OpenAPI and tests use synthetic values only.Failure and rollback
Validation or mapping failure activates nothing. A process crash/replay resumes or deterministically returns the stored result without duplicate revisions. Changed checksum, unsupported schema/status, missing source, ambiguous mapping, overlapping provider/native coverage, count mismatch, stale revision, or concurrent activation quarantines the candidate and preserves the last accepted public total.
Rollback is a confirmed, reasoned, idempotent pointer transition to the prior accepted aggregate set, increments the public total revision, and invalidates the event detail. It never deletes native registrations, creates legacy rows, rewrites another provider, sends mail, or exposes protected data.
Source retention/deletion remains outside the aggregate database and follows #64's approved protected-source handling. The application retains only minimized non-PII aggregate/provenance/audit data. No failure triggers verification/confirmation/newsletter/transactional email, sponsor sharing, consent creation, public identity, or provider write-back.
Non-goals
EventRegistration, answer, consent, email, name, person, source guest ID/digest, attendee timestamp, or other row-level PII storage/import.Acceptance criteria
N registered; incomplete/review/source-missing state is not shown as zero, and no public route/serializer/cache/search/sitemap/structured data exposes attendee identity or provider breakdown.uv/Make targets.Automated and failure scenarios
Browser acceptance
At desktop about 1440×900 and mobile about 390×844 using synthetic fixtures only:
/eventsto/events/<slug>; verify exact0 registered,1 registered, and plural-count rendering where coverage is complete, plus an omitted count for incomplete/quarantined evidence.Required specification and issue amendments
Implementation updates specifications 02, 05, 06, 07, 09, and 10 for the aggregate overlay, exact public count, no-public-identity boundary, provider-neutral management, protected-source minimization, replacement/deduplication, invalidation, rollback, and verification.
#45, #46, and #47 are amended now: public event pages may expose only #112's exact aggregate
registeredtotal and never an attendee identity/list. #111 is amended to remove the abandoned attendee-identity exception and row-import dependency.Dependencies and delivery
Because protected-source reconciliation requires an authorized operator, implementation commits use
Refs #112, retainhuman, and leave the issue open until the HUMAN criterion passes. No protected export, mapping values, attendee data, provider credential, runtime state, or screenshot is committed.