Add script to purge private applicant answers for a concluded round - #43
Merged
Conversation
Adds scripts/purge-private-answers.ts (and an applications:purge-private-answers
deno task) to forget private applicant data after a round concludes.
Private answers live in application_answers.answer (jsonb); an answer is private
when its field's application_form_fields.private flag is true. The script nulls
out those values in place, scoped to a single round, forgetting the content
while leaving row structure and referential integrity intact.
Safety:
- Scoped to one round via --round=<uuid>.
- Dry-run by default; writes only with --confirm, inside a transaction.
- With no --round, lists rounds with their remaining private-answer counts.
- Idempotent (only targets answer IS NOT NULL).
- Invalidates cached admin/submitter reads after the purge.
Writes genuine SQL NULL, which mapDbAnswersToDto surfaces as { <value>: null };
the frontend renders this as "No answer provided" for every field type,
including in the admin review flow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an operational script and Deno task to permanently purge (“NULL out”) private application answers for a specific round, while keeping answer rows intact and invalidating relevant application caches afterward.
Changes:
- Added
scripts/purge-private-answers.tsto list candidate rounds, dry-run purge targets, and (with--confirm) updateapplication_answers.answerto SQLNULLforapplication_form_fields.private = truescoped to one round. - Added
applications:purge-private-answerstask entry todeno.json.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| scripts/purge-private-answers.ts | Implements round-scoped listing, dry-run reporting, transactional purge update, and cache invalidation. |
| deno.json | Adds a Deno task to run the new purge script. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
scripts/purge-private-answers.tsand anapplications:purge-private-answersdeno task to forget private applicant data once a round has concluded.Private answers live in
application_answers.answer(jsonb) — an answer is "private" only because its field'sapplication_form_fields.privateflag istrue(there's no dedicated table or encryption; the app just filters these out at read time). This script nulls out those values in place, scoped to a single round: it forgets the content while leaving the row structure and referential integrity intact.Usage
DB_CONNECTION_STRINGis read from the env file, same as the other scripts.Safety
--round=<uuid>, throughversions → applications.round_id, so it can't touch another round.--confirm, inside a transaction.answer IS NOT NULL.Rendering safety (verified)
The script writes genuine SQL
NULL.mapDbAnswersToDtosurfaces that as{ <value>: null }(e.g.{ type: 'text', text: null }), keeping the answer object and itstypeintact. The frontend answer schemas are all.nullable()and the render component branches on=== nullfor every field type, showing "No answer provided" — including in the admin review flow where private answers are visible. No page-level parse failures, no crashes.Scope note
Per the intended use, this only touches
private=trueanswers. It deliberately does not redactemail-type answers that aren't flagged private, nor KYC PII (kyc_requests) — those can be added as a follow-up if wanted.🤖 Generated with Claude Code