mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 23:12:04 +00:00
fix(cycle): second-audit safety, privacy, and write-path hardening
Address the second-round audit (server cluster): - AVOID_PREGNANCY surfaces the fertile window, so it must carry the stronger "not a contraceptive method" disclaimer — wire the existing strong copy via a shared cycleDisclaimerKey(goal), server and client. - Re-anchor the FOLLOWING neighbour cycle on a back-filled period start so an inserted-between-two-cycles start no longer leaves a stale open cycle. - Resolve the sensitive-category encryption flag once per bulk/import batch instead of re-reading the profile per day; hoist the bulk cycle attribution in-memory (no per-entry query). - Drop the raw driver message from the bulk wide-event meta (annotate meta is not run through the central redactor); keep the stable reason + code. - Hard-purge now clears the cycle audit trail and the cycle reminder rows in the push-attempts ledger, so no dated reproductive trace survives. - Discreet mode collapses the Web-Push coalescing tag and drops cycle metadata (phase) from APNs, matching the existing APNs event collapse. - Add the reproductive intent field names to the central redactor denylist. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
31a14ce7bf
commit
3375bbabac
11 files changed
+184
-21
No files matched your search
@@ -4,12 +4,14 @@
|
||||
* Distinct from the per-row soft-delete (`DELETE /api/cycle/day-logs/{id}`,
|
||||
* which leaves a tombstone for the sync feed): this HARD-deletes every
|
||||
* cycle row the user owns — day-logs (and their symptom links, by cascade),
|
||||
* menstrual cycles, predictions, and the reminder ledger — so nothing
|
||||
* reproductive persists in Postgres. The `CycleProfile` row is reset to its
|
||||
* defaults (tracking left enabled) rather than dropped, so the gate and
|
||||
* settings page keep working.
|
||||
* menstrual cycles, predictions, the cycle audit trail, and the cycle
|
||||
* reminder-delivery rows in the push-attempts ledger — so no dated
|
||||
* reproductive trace survives in Postgres (the post-Dobbs threat model). The
|
||||
* `CycleProfile` row is left in place (gate + settings keep working).
|
||||
*
|
||||
* Gated (`cycle.disabled` 403) and owner-scoped. Audited.
|
||||
* Gated (`cycle.disabled` 403) and owner-scoped. The purge action itself IS
|
||||
* audited (written after the transaction, so it survives) — its `details`
|
||||
* carry only counts, no reproductive content.
|
||||
*/
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
@@ -39,10 +41,34 @@ export const DELETE = apiHandler(async (request: NextRequest) => {
|
||||
const cycles = await tx.menstrualCycle.deleteMany({
|
||||
where: { userId: user.id },
|
||||
});
|
||||
// The cycle audit trail carries dated reproductive-adjacent details
|
||||
// (period boundaries, the AVOID_PREGNANCY goal-nudge, goal changes). A
|
||||
// purge that promises "nothing reproductive persists" must clear it too.
|
||||
// The purge's own audit row is written AFTER this transaction, so it
|
||||
// survives (counts only, no reproductive content).
|
||||
const auditRows = await tx.auditLog.deleteMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
OR: [
|
||||
{ action: { startsWith: "cycle." } },
|
||||
{ action: "user.cycle-prefs.update" },
|
||||
],
|
||||
},
|
||||
});
|
||||
// The shared push-attempts ledger holds dated cycle reminder-delivery
|
||||
// rows (eventType + createdAt) for up to 90 days — drop the cycle ones.
|
||||
const pushRows = await tx.pushAttempt.deleteMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
eventType: { in: ["CYCLE_PERIOD_SOON", "CYCLE_PERIOD_CONFIRM"] },
|
||||
},
|
||||
});
|
||||
return {
|
||||
dayLogs: dayLogs.count,
|
||||
predictions: predictions.count,
|
||||
cycles: cycles.count,
|
||||
auditRows: auditRows.count,
|
||||
pushRows: pushRows.count,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user