feat(backup): carry the setup answers out of the account and back in

Nothing recomputes an answer. The module map they produced survives on the
account row, but the map says what is switched on, not that the person told the
flow they take medication daily and have a visit next month — so a restore
without the row opens a blank questionnaire for somebody who already answered
it, and leaves the checklist ordering itself from nothing.

Both ends live in one file, the arrangement the visits and the score already
use, because a reader who greps only the restore route gets a false negative
when the route delegates.

`modulesDerivedAt` is the field that makes this more than history and it is
asserted rather than defaulted: it is the once-only latch on the module
derivation, so a restore that let it default to null would hand the next confirm
permission to re-apply the questionnaire over every module decision taken since,
silently. The round trip seeds it non-null and reads it back.

The file's schema validates the three answer objects loosely and the restore
narrows them through the same fail-soft readers the account payload uses. Being
stricter there would refuse a whole account's restore over one retired chip.

The wipe takes the row for the reason the reset already takes the wizard's own
columns: the record the answers were about is gone, so the answers describe
nothing.
This commit is contained in:
Marc-André Bombeck committed 2026-09-10 11:03:56 +02:00
1 parent dbfad0f28e
commit 3f44b882ac
11 files changed
+376

No files matched your search

@@ -259,6 +259,10 @@ function sourceClient() {
},
]),
},
// The setup answers. Absent for this fixture's account, which is the
// ordinary case: the export has to answer for a record that never entered
// the flow without inventing one.
onboardingRecord: { findUnique: vi.fn().mockResolvedValue(null) },
// The score as it was SHOWN on a local day. Not recomputable: today's
// number always comes from today's rows, so once the readings behind this
// day moved, nothing can reproduce what it said.
@@ -57,6 +57,7 @@ import {
import { restoreProfileData } from "@/lib/export/profile-backup";
import { restoreIntradayProfileData } from "@/lib/export/intraday-profile-backup";
import { restoreHealthScoreData } from "@/lib/export/health-score-backup";
import { restoreOnboardingData } from "@/lib/export/onboarding-backup";
import { restoreVisitsData } from "@/lib/export/visits-backup";
import { restoreVaccinationsData } from "@/lib/export/vaccinations-backup";
import { restoreSensitiveData } from "@/lib/export/sensitive-backup";
@@ -111,6 +112,7 @@ interface RestoreResponse {
correlationPatterns: number;
intradayProfiles: number;
healthScoreRecords: number;
onboardingRecords: number;
practitioners: number;
encounters: number;
encounterLinks: number;
@@ -1377,6 +1379,14 @@ const handler = apiHandler(
payload,
);
// The needs-based setup answers. Both ends live in
// `src/lib/export/onboarding-backup.ts`, same as the three above.
const onboardingCleared = await restoreOnboardingData(
tx,
ownerId,
payload,
);
const biomarkerByName = new Map<string, string>();
const restoredBiomarkerIds = new Set<string>();
for (const biomarker of payload.biomarkers) {
@@ -1979,6 +1989,7 @@ const handler = apiHandler(
correlationPatterns: profileCleared.correlationPatterns,
intradayProfiles: intradayCleared.intradayProfiles,
healthScoreRecords: healthScoreCleared.healthScoreRecords,
onboardingRecords: onboardingCleared.onboardingRecords,
practitioners: visitsCleared.practitioners,
encounters: visitsCleared.encounters,
encounterLinks: visitsCleared.encounterLinks,