DataTalksClub / DataTalksClub/website
Add capability-scoped Member Studio and admin correction and Slack resend
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
Parent epic: #108
PM pre-grooming status — blocked, not engineer-ready
This intake has been reconciled against the authoritative MemberProfile, Studio/admin API, security, audit, and Slack-delivery contracts. It deliberately retains needs grooming: the target product behavior is mostly fixed, but engineering cannot start until the dependency gate below supplies the final registered production policy symbols and the accepted manual-resend service/rate contract. A PM must perform one final dependency-identity reconciliation and remove needs grooming only after those artifacts exist.
Product outcome
Give an authorized support operator one private Members workspace in Studio and exact /api/v1/admin/ parity for bounded member-profile lookup, masked inspection, allowlisted correction, and an explicit Slack-access email resend. Both adapters call the same accounts/email application services, use opaque MemberProfile UUIDs, preserve optimistic concurrency and idempotency, and produce append-only redacted audit evidence. This is private support tooling; it never creates, links, edits, or grants authority to content.Person, and it never exposes the shared Slack join URL.
Normative authority
_docs/PROCESS.md— role-separated issue lifecycle and versioned verification._docs/architecture/app-boundaries.mdand_docs/architecture/shared-primitives.md—accountsowns profile/Slack commands;email_appowns logical delivery intent and redacted Relay projection; Studio/API are adapters; network work occurs only after commit through durable jobs._docs/specs/06-studio-and-admin-api.md, especially “Management parity contract”, “Members”, “Admin API coverage”, “API conventions”, “Audit model”, and “Safety requirements”._docs/specs/07-security-privacy-operations.md— deny-by-default function/object/field authorization, private/no-store handling, abuse limits, redaction, and MemberProfile/Person separation._docs/specs/10-verification-strategy.md— management parity, security, privacy, browser, screenshot, and artifact-canary evidence.- #247 — exact MemberProfile UUID, 13-field schema, revision/confirmation transitions, collision-safe identity resolution, and support-correction semantics.
- #248 — exact editable representation/validation and compatibility projection through the shared accounts service.
- #249 — grant eligibility, invite-version/secret boundary, deny/cancel hook, and the rule that manual resend belongs here rather than in member self-service.
- #251 — accepted accounts/member privacy adapter, field disposition, export/correction/erasure hooks, and fail-closed production-registration boundary. This issue is downstream of #251; #251 never depends on #250.
- #49 — logical
EmailDelivery, Relay-authoritative projection, manual-resend, ambiguity, suppression, reconciliation, and durable after-commit transport boundary. - #288 — owner-approved shared masked-member presentation consumed by both Member and courses management surfaces.
- #32/#33 — production Studio/admin API role, high-risk, identity, credential, rate, OpenAPI, and parity foundations.
- Closed owner decisions #20/#28 — capability authorization remains separate from Django
is_staff; sends require recent reauthentication plus an action-specific “are you sure” confirmation; no dual approver is required now.
Hard dependencies and final start gate
This issue is BLOCKED on owner-approved #288, accepted and integrated #32, #33, #247, #248, #249, #251, and the applicable ordinary-delivery/manual-resend portion of #49. The deployed HUMAN Slack-secret exercise on #249 is not required for synthetic local engineering if #249’s code/service contract has completed its tester/PM/merge/on-call gates; it remains required before #108 can close.
Before removing needs grooming, PM must record:
- accepted merge SHAs for #32, #33, #247, #248, #249, #251, and the applicable #49 slice, plus the final
accountsmigration leaf; - the final registered names/signatures for capability, object/field policy, active-staff/reauthentication, audit, API-principal, pagination, error-envelope, and idempotency primitives supplied by #32/#33;
- #49’s exact public manual-resend command/result symbols and retention/idempotency scope, #249’s current-invite resolver/version interface, and #251’s exact member-domain privacy adapter/version plus production-registration state, without reading protected data or recording a secret;
- the final per-subject business resend limit and registry
rate_cost. The current management foundation establishes a 60-second per-principal window of 120 read units and 30 write units, maximum page size 100, and rate costs 1–10, but no authoritative Member Slack-resend business interval/cost is yet accepted; - the exact default masked-identifier presentation approved under #288; neither this issue nor engineering may invent, fork, or weaken it.
The final PM pass may reconcile symbol names, consume #288, and settle the two Slack-resend values only; it must not broaden this product scope.
Exact capabilities, roles, and authorization
Register exactly these product capabilities unless the accepted #32/#33 registry requires a mechanically different key while preserving the same authority:
accounts.member_profile.view_pii— reveal the allowlisted full profile values in an otherwise authorized Members result;accounts.member_profile.correct— list/detail access through the authorized member queryset and allowlisted profile correction;accounts.slack_access.manage— inspect safe grant/delivery summary and request Slack resend.
support_operator receives the bounded Members list/detail and approved corrective actions, with identifiers/profile values masked by default. Full PII requires accounts.member_profile.view_pii. Correction additionally requires accounts.member_profile.correct; resend additionally requires accounts.slack_access.manage. site_admin may receive these through the accepted role matrix. auditor, email/course/content/event operators, ordinary staff, learner sessions, and principals lacking the exact scope are denied unless the accepted role registry explicitly composes the same capability.
Every query applies active-principal, function, object, and sensitive-field policy before lookup. A UUID outside the authorized queryset and a nonexistent UUID return the same generic result. A PII capability cannot expand object scope; a correction/resend capability cannot imply full-PII access. Service principals use the final #33 scopes and never inherit a human role by name. Generic is_staff, superuser, email domain, profile content, Slack state, or content.Person relationship is never sufficient authorization.
Exact routes and adapter parity
Studio
GET /studio/members/— bounded list/search, completion and folded Slack-delivery state.GET /studio/members/<uuid>/— masked detail plus safe profile/grant/delivery summary.POST /studio/members/<uuid>/correct/— correction confirmation/submission through the shared accounts command.POST /studio/members/<uuid>/slack-resend/— reauthenticated explicit review/confirmation and resend command.
All Studio POSTs require CSRF. The correction form carries current decimal revision and a generated idempotency token for double-submit safety. Resend carries a generated idempotency token and the final #32 recent-reauthentication evidence. No mutation is performed by GET.
Admin API
GET /api/v1/admin/member-profilesGET /api/v1/admin/member-profiles/<uuid>PATCH /api/v1/admin/member-profiles/<uuid>POST /api/v1/admin/member-profiles/<uuid>/slack-resend
Use final #33 Bearer principals/scopes, JSON only, UUID route identifiers, no CORS widening, and the generated OpenAPI 3.1 document. Suggested stable operation IDs are member_profiles.list, member_profiles.retrieve, member_profiles.correct, and member_profiles.slack_resend; final grooming must reconcile only if #33 enforces a different mechanical convention.
Studio and API adapters must register the same service, permission/object/field policy, redaction policy, safe result, and side effects. The resend success is immediate logical-intent acceptance, not proof of Relay/provider delivery; if #49 requires an operation resource, both adapters expose its same safe state.
Bounded list, search, sort, and representation
The management API uses the accepted page-number contract: default page size 20, maximum 100, exactly one value per query parameter, and safe 400 invalid_query for repeated/unknown/invalid parameters. Studio uses the same query service and never loads more than 100 result rows for one page.
Allowlist filters only for final safe state enums plus one bounded operator search input. Search input is trimmed, maximum 254 Unicode characters, and passed only to the accounts-owned normalized identity lookup; no fuzzy substring scan over profile free text or URLs. The authorized queryset is established before search/lookup. Missing, collision, quarantine, absorbed/broken alias, inactive, deleted, and unauthorized targets never disclose which condition occurred. Sort is limited to stable non-PII fields such as update/completion state and opaque UUID; do not sort or search about, ambitions, why_joined, URLs, organization, country suggestion, Slack URL, provider reason, or raw Relay payload.
Default list/detail returns only the opaque profile UUID, safe completion/revision timestamps or counters required for support, current eligibility/invite-version number, and folded logical delivery state. It never contains the raw join URL, email body/context, provider payload/reason, Relay credential/message detail, profile free text/URLs, or an unmasked direct identifier. Full-PII mode, when separately authorized, contains only the 13 allowlisted MemberProfile values below and the minimum verified account identifier approved in final grooming; it never serializes identity candidates, normalized-email internals, legacy alias rows, member_confirmed_revision, secret references, or Person data.
Every response is private zero-TTL with Cache-Control: private, no-store and X-Robots-Tag: noindex, nofollow; Studio/API/member routes remain absent from sitemap, search, public serializers, and analytics carrying profile identifiers/values.
Exact correction contract
Correction may write only these 13 #247/#248 scalar fields:
country, work_status, organization, professional_role, seniority, about, ambitions, why_joined, github_url, linkedin_url, website_url, certificate_name, preferred_timezone.
It may not write user/email/verification/identity status, profile/user UUID, completion metadata, revision fields, compatibility columns directly, Slack grant/invite/delivery state, Person fields/links, roles, course records, or arbitrary model attributes. Unknown, duplicate, wrongly typed, or nested fields fail as mass assignment with zero mutation. The shared #247/#248 accounts service performs the exact trimming, choices, country, safe-URL, timezone, length, completion, identity, and compatibility-projection validation.
Correction requires:
- the target’s current revision (
If-Matchon API; bound current revision on Studio); - one bounded code-owned support reason selected from the final accepted #32 reason vocabulary, never a profile value or free-form PII dump;
- an idempotency context sufficient to prevent Studio double-submit and preserve one command/audit result.
API If-Match uses the final #33 strong revision syntax. Missing is 428 precondition_required; malformed/foreign is 400 invalid_precondition; valid stale is 409 stale_revision. A successful correction advances profile revision exactly once, updates the #248 compatibility projection atomically, and does not advance member_confirmed_revision. Consequently, a previously complete profile becomes unconfirmed for confirmed-scalar consumers until its owner reconfirms; the operator UI must say this before submission and show the safe result afterward. Rollback, validation failure, stale/conflicting replay, audit failure, or projection failure changes nothing.
Ordinary profile correction is not one of #28’s enumerated reauthentication-required action classes. It requires active current staff authorization, CSRF/Bearer controls, revision, reason, confirmation, idempotent handling, and audit, but no additional MFA step-up unless final #32 explicitly classifies this exact operation. Viewing PII is audited but does not itself grant correction or resend.
Exact Slack resend contract
Resend is a high-risk send under resolved #28. It requires:
accounts.slack_access.manageand target membership in the authorized queryset;- recent reauthentication through final #32 for Studio, and the final #33 API-equivalent proof for a human API principal;
- an explicit action-specific “are you sure” review showing only safe target summary, purpose
slack_access, current invite version number, expected count1, and the bounded reason; Idempotency-Keyin the API and an equivalent generated one-use token in Studio;- the final per-principal/profile business rate rule and management registry cost recorded at final grooming.
The command re-resolves current eligible survivor/profile/grant state immediately before acting. Inactive, quarantined, deleted, colliding, broken/cyclic alias, unverified, profile/grant mismatch, disabled future eligibility, absent grant, or invalid invite-version state fails generically and creates no delivery/job/secret read. Suppressed, hard-bounced, complained, dead, or ambiguous prior delivery follows #49’s accepted manual-resend policy; ambiguity is never silently retried. If manual resend is disallowed for the current projection, return the same safe actionable denial defined by #49 rather than bypassing it.
An accepted resend creates one new #49 logical EmailDelivery intent/key linked to the prior/current Slack purpose and pinned to the current positive invite version. Exact replay returns the original safe result; same key with changed target, recipient snapshot, reason code, template/sender/context, or invite version conflicts without new work. Transaction rollback creates neither intent nor durable job. The Slack join secret is resolved only by the accepted leased #49 handler after commit, never by Studio/API, the command transaction, preview, audit, OpenAPI, test report, or screenshot. Relay timeout/unavailability is a truthful delayed/retryable/ambiguous safe result; there is no direct Relay/SES/Datamailer fallback and no claim that accepted means delivered.
No member-facing resend route is added. Profile/email edit, reconfirmation, invite rotation, page refresh, retry, or delivery reconciliation never auto-resends.
Audit and redaction
Use append-only audit actions for allowed and denied PII view, correction attempt/result/replay/conflict/stale, and resend preview/attempt/result/replay/rate/denial. Record only actor/principal reference, target type and opaque UUID, action/outcome, request/correlation/idempotency hash, revision/invite-version number, bounded reason code, changed field names, expected count, safe folded state, and safe error/result code.
Never audit or place in logs/metrics/traces/errors/artifacts: old/new profile values; email or normalized identity; organization/country suggestion/free text/URLs; raw or masked value capable of reversal; Slack URL or secret reference; email body/context; provider payload/reason; authorization/session/CSRF/API credentials; idempotency token; or content.Person data. Audit persistence failure rolls back a correction or resend command rather than producing unaudited business state.
Admin API errors and OpenAPI
Use the accepted #33 envelope: { "error": { "code", "message", "request_id", "fields"? }, "result"? }. Messages and field codes are code-owned and contain no dynamic target/value/provider evidence. Required cases include authentication_required (401), permission_denied (403), generic not_found (404), invalid_query/invalid_request/invalid_precondition/invalid_idempotency_key (400), precondition_required (428), stale_revision/idempotency_conflict (409), unsupported media/method/body limits (415/405/413), rate_limited with bounded Retry-After (429), and safe dependency/policy unavailable results with no fallback.
Generated OpenAPI must declare all four routes/operations, exact scopes/capabilities, schemas, writable/filter/sort fields, pagination, If-Match, Idempotency-Key, rate class/cost, success status/operation behavior, safe error responses, and redacted synthetic examples. It must contain no email, profile value, Slack URL, secret/reference name, provider value, real UUID, or credential canary.
Acceptance criteria
- Final dependency identities, migration leaf, production symbols, masking presentation, reason vocabulary, and resend business-rate/cost contract are recorded;
needs groomingis removed only then. - Exact Studio/API routes and four registered capabilities have query/command, authorization, service, audit, rate, schema, concurrency/idempotency, safe-result, and OpenAPI parity.
- List/search/detail are bounded, authorized before lookup, masked by default, separately PII-gated, non-enumerating, private/no-store/noindex, and secret/provider/Person-free.
- Correction accepts only the 13 fields, uses shared validation/projection, reason and current revision, advances revision once without advancing member confirmation, and rolls back on every failure.
- Resend enforces current eligibility, #28 reauthentication/explicit confirmation, reason, current invite version, idempotency, business/management rate limits, and #49 ambiguity/suppression semantics.
- Exact resend replay creates one new linked logical intent/job; changed replay conflicts; no network/secret read occurs before commit and no direct sender fallback exists.
- Allowed/denied/stale/replayed/conflicting/rate/dependency outcomes have redacted audit parity and zero sensitive artifact leakage.
- No public/member resend, bulk export/mutation, content.Person side effect, course mutation, privacy-engine implementation, provider mutation, or Django-admin expansion is introduced.
- Focused Django/service/security/concurrency/rate/audit/OpenAPI/parity/cache tests, migration drift, container checks, and the graph-selected versioned verification plan pass with no required skip.
- Independent tester Playwright and inspected screenshots pass the desktop/mobile scenarios below using synthetic data and no captured PII/secret.
Required Django, integration, and security scenarios
- Positive/negative matrix for support/site-admin/auditor/other operators, composed roles, human/API principal, active/revoked staff, object scope, field-level PII, correction, and resend capabilities.
- Authorized queryset before exact/search lookup; unknown/unauthorized/collision/quarantine/inactive/absorbed/broken/deleted targets are non-enumerating. Repeated/unknown filters, overlong search, unsupported sort, page bounds, and query-count ceilings pass.
- All 13 correction fields and every unknown/read-only field; trim/choice/country/Unicode/length/safe URL/timezone rules; mass assignment; stale/concurrent updates; projection/audit rollback; member reconfirmation after support correction; zero Person side effects.
- Resend allowed, denied, stale reauth/API proof, cancel, invalid reason, absent/mismatched grant, disabled eligibility, version mismatch, exact replay, changed-key conflict, concurrent double submit, rate exhaustion/storage outage, DB rollback, and after-commit durable dispatch.
- Every #49 prior projection, including pending/accepted/delivered/retryable/ambiguous/suppressed/dead/hard-bounced/complained, with truthful safe result and no automatic ambiguity retry.
- Redaction canaries across responses, OpenAPI, audit, logs, metrics, traces, database/job/context records, screenshots, browser history, test reports, and issue-ready evidence.
- Studio/API parity for successful result and every denial/error; CSRF, Bearer scope, private headers, CORS denial, body/time limits,
If-Match, idempotency andRetry-Afterbehavior. - Query-count assertions remain bounded with page size; no per-row identity/profile/grant/delivery query or serializer leak.
Browser, accessibility, and screenshot scenarios
The independent tester uses synthetic identities and captures under .tmp/screenshots/ at about 1440×900 and 390×844, then opens and inspects every image. Screenshots must use approved synthetic display placeholders and must never contain an email, profile free text/URL, raw Slack link, secret/reference, provider payload, session/CSRF/API credential, real account data, or reversible masked identifier.
- Members empty state and bounded populated list/search/pagination with masked rows and clear separation from Studio People.
- Masked detail for ordinary support; separately authorized full-PII detail; field/object denial and another/nonexistent UUID remain non-enumerating.
- Correction review explains changed field names, reason, revision, and required member reconfirmation; success, validation, stale revision, cancel, denied, and preserved-input error states are keyboard/screen-reader/zoom/reflow usable.
- Resend review shows safe purpose/version/count only; stale reauthentication/API-equivalent handoff, explicit confirm, cancel, delayed/ambiguous/suppressed/rate-limited/denied/success states never reveal a link or claim delivery.
- No-JavaScript Studio forms work with CSRF; focus moves to a linked error summary; status is not color-only; controls meet WCAG 2.2 AA target/focus/reflow behavior.
- Logout, revoked staff, account switch, browser back/forward, and warmed-cache navigation cannot recover prior PII or support state.
Explicit non-goals and protected boundaries
- No public member directory, public profile, Person creation/link/inference/synchronization, editorial authority, or People editor.
- No bulk member export, bulk correction/resend, arbitrary field editor, course/enrollment/certificate change, or generic privacy-rights engine (#64 owns global rights workflows).
- No member-facing resend, recurring Slack preference, automatic resend, Slack API/SCIM/directory/membership lookup/revocation, secret rotation UI, template editor/renderer/store, provider-attempt/event store, or direct Relay/SES/Datamailer call.
- No raw Slack URL or secret in any Studio/API/OpenAPI/audit/log/metric/trace/database/job/screenshot/report/issue artifact.
- No production data, provider call, secret inspection, infrastructure apply, deployment, or HUMAN Slack exercise in automated delivery.
- No replacement or expansion of Django admin. It remains the separately protected, production-guarded emergency surface governed by #20/#32 and is not normal management UI. This issue neither registers a parallel unrestricted MemberProfile editor nor weakens the current fail-closed production guard/direct-database last-resort boundary.
Delivery convention
After final PM grooming removes needs grooming, follow _docs/PROCESS.md: engineer leaves an uncommitted frozen candidate; an independent tester verifies the exact plan and screenshots; PM accepts; then a focused commit uses Closes #250, local main is merged with --no-ff and pushed without a pull request, and on-call alone observes CI. If #249’s deployed HUMAN criterion is still pending, that keeps #249/#108 open; it does not turn #250 into a secret/provider action.
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
Start with _docs/specs/06-studio-and-admin-api.md, _docs/specs/07-security-privacy-operations.md, and _docs/specs/10-verification-strategy.md, then review the accepted contracts from #32, #33, #247, #248, #249, #251, #288, and the applicable #49 slice. Work cannot begin until the listed production symbols, resend contract, policy values, and dependency merge SHAs are reconciled; done means the final gate is satisfied and both Studio and admin API parity requirements are verified.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, authentication, backend-api-design, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100