mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
The SETTING verdicts are now the keys of the codec table, so a column becomes a setting by getting a codec and nothing else has to name it. The verdicts for every other User column move beside it into account-settings-classes.ts, and the restore half into account-settings-restore.ts. The backup plan keeps the model verdicts. The classification guard now also refuses a column that has both a codec and another verdict, since the composed table would quietly carry it as a setting.
157 lines
7.7 KiB
TypeScript
157 lines
7.7 KiB
TypeScript
/**
|
|
* The verdict for every `User` column that is not a setting.
|
|
*
|
|
* Part of the account-settings domain: `account-settings-backup.ts` composes
|
|
* these with the settings its codec table declares into
|
|
* `USER_COLUMN_BACKUP_CLASS`.
|
|
*/
|
|
/**
|
|
* Every column of the `User` row, and whether it travels.
|
|
*
|
|
* The models (`backup-plan.ts`) are decided one table at a time. The account row cannot
|
|
* be: it holds the person's own choices next to their sign-in credentials,
|
|
* the integration keys, the operator's policy for the account and a dozen
|
|
* cursors, and a single verdict for the whole row would either leave the
|
|
* choices behind or carry the credentials with them. Before this table the
|
|
* row was simply not in the backup, so a restore brought the record back and
|
|
* dropped units, language, time zone, modules, reminder thresholds and the
|
|
* rest of how the account had been set up.
|
|
*
|
|
* `SETTING` — something the person chose. Exported in the
|
|
* `accountSettings` section and written back to the account
|
|
* being restored, column by column, after the same checks
|
|
* the settings routes apply. A setting is a column with a
|
|
* codec in `ACCOUNT_SETTING_CODECS`
|
|
* (`account-settings-backup.ts`), which is where its verdict
|
|
* comes from; this file holds every other verdict.
|
|
* `CREDENTIAL` — a secret, a token, a provider registration or an external
|
|
* identity binding, and the state that only means something
|
|
* next to one. A backup file is not a credential store; the
|
|
* same reasoning as `ApiToken` in the backup plan.
|
|
* `IDENTITY` — what the account IS rather than how it is set up: its key,
|
|
* its sign-in name, its role, whether it is a managed record,
|
|
* and the limits the operator set for it. A restore puts data
|
|
* back into an account; it never changes which account that
|
|
* is or what the operator allowed it.
|
|
* `OPERATIONAL` — cursors, caches and timestamps this host wrote while
|
|
* working for the account. Carrying them would assert work
|
|
* the receiving host never did, the reason `IntegrationStatus`
|
|
* and `ProviderHealth` are excluded from the backup.
|
|
*
|
|
* `src/__tests__/user-column-backup-classification.test.ts` reads the
|
|
* schema and fails on a column that is missing here, so a new column gets a
|
|
* decision rather than a default.
|
|
*/
|
|
export type UserColumnBackupClass =
|
|
"SETTING" | "CREDENTIAL" | "IDENTITY" | "OPERATIONAL";
|
|
|
|
/** The `User` columns that are not settings, each with its verdict. */
|
|
export const USER_COLUMNS_OUTSIDE_SETTINGS = {
|
|
// ── Identity ──────────────────────────────────────────────────────────────
|
|
id: "IDENTITY",
|
|
username: "IDENTITY",
|
|
email: "IDENTITY",
|
|
role: "IDENTITY",
|
|
createdAt: "IDENTITY",
|
|
updatedAt: "IDENTITY",
|
|
// Whether somebody else runs this record. Account shape, and the gate for
|
|
// the guardian's reach: the wipe keeps it for the same reason.
|
|
managedProfileAt: "IDENTITY",
|
|
// The operator's per-account policy and quota, set by the operator and not
|
|
// by the person; a file from Monday must not undo Tuesday's decision.
|
|
mfaEnforced: "IDENTITY",
|
|
documentQuotaBytes: "IDENTITY",
|
|
|
|
// ── Credentials ───────────────────────────────────────────────────────────
|
|
passwordHash: "CREDENTIAL",
|
|
oidcIssuer: "CREDENTIAL",
|
|
oidcSub: "CREDENTIAL",
|
|
totpSecretEncrypted: "CREDENTIAL",
|
|
totpConfirmedAt: "CREDENTIAL",
|
|
totpLastStep: "CREDENTIAL",
|
|
codexAccessTokenEncrypted: "CREDENTIAL",
|
|
codexRefreshTokenEncrypted: "CREDENTIAL",
|
|
codexTokenExpiresAt: "CREDENTIAL",
|
|
codexConnectedAt: "CREDENTIAL",
|
|
codexConnectionStatus: "CREDENTIAL",
|
|
aiAnthropicKeyEncrypted: "CREDENTIAL",
|
|
aiLocalKeyEncrypted: "CREDENTIAL",
|
|
aiOpenaiKeyEncrypted: "CREDENTIAL",
|
|
aiCompatKeyEncrypted: "CREDENTIAL",
|
|
// The bot token, the chat it is bound to, and the switch that only means
|
|
// something with both: the same reasoning as `NotificationChannel`.
|
|
telegramBotToken: "CREDENTIAL",
|
|
telegramChatId: "CREDENTIAL",
|
|
telegramEnabled: "CREDENTIAL",
|
|
withingsClientIdEncrypted: "CREDENTIAL",
|
|
withingsClientSecretEncrypted: "CREDENTIAL",
|
|
whoopClientIdEncrypted: "CREDENTIAL",
|
|
whoopClientSecretEncrypted: "CREDENTIAL",
|
|
fitbitClientIdEncrypted: "CREDENTIAL",
|
|
fitbitClientSecretEncrypted: "CREDENTIAL",
|
|
googleHealthClientIdEncrypted: "CREDENTIAL",
|
|
googleHealthClientSecretEncrypted: "CREDENTIAL",
|
|
// The address is stored sealed beside its token because it can carry one,
|
|
// and the private-host exemption is a security decision about that one
|
|
// connection, not a preference that should arrive ahead of it.
|
|
nightscoutUrlEncrypted: "CREDENTIAL",
|
|
nightscoutTokenEncrypted: "CREDENTIAL",
|
|
nightscoutAllowPrivateHost: "CREDENTIAL",
|
|
// The Dawarich connection, credentials like the Nightscout pair. The
|
|
// sealed preferences name a place id on that instance, which means nothing
|
|
// without the key, so they stay behind with it.
|
|
dawarichUrlEncrypted: "CREDENTIAL",
|
|
dawarichApiKeyEncrypted: "CREDENTIAL",
|
|
dawarichPrefsEncrypted: "CREDENTIAL",
|
|
dawarichAllowPrivateHost: "CREDENTIAL",
|
|
polarAccessTokenEncrypted: "CREDENTIAL",
|
|
polarUserIdEncrypted: "CREDENTIAL",
|
|
polarClientIdEncrypted: "CREDENTIAL",
|
|
polarClientSecretEncrypted: "CREDENTIAL",
|
|
ouraAccessTokenEncrypted: "CREDENTIAL",
|
|
ouraRefreshTokenEncrypted: "CREDENTIAL",
|
|
ouraClientIdEncrypted: "CREDENTIAL",
|
|
ouraClientSecretEncrypted: "CREDENTIAL",
|
|
// What the Oura grant on this host does not cover; meaningless beside a
|
|
// grant that does not travel, and found out again by the first sync.
|
|
ouraScopeDenied: "CREDENTIAL",
|
|
ouraUserId: "CREDENTIAL",
|
|
// Names the guardian whose provider app the record borrows; the app itself
|
|
// does not travel, so neither does the marker.
|
|
clientAppSharedBy: "CREDENTIAL",
|
|
stravaClientIdEncrypted: "CREDENTIAL",
|
|
stravaClientSecretEncrypted: "CREDENTIAL",
|
|
stravaAccessTokenEncrypted: "CREDENTIAL",
|
|
stravaRefreshTokenEncrypted: "CREDENTIAL",
|
|
stravaAthleteId: "CREDENTIAL",
|
|
|
|
// ── Operational ───────────────────────────────────────────────────────────
|
|
insightsCachedAt: "OPERATIONAL",
|
|
insightsCachedTextEncrypted: "OPERATIONAL",
|
|
insightsCachedLocale: "OPERATIONAL",
|
|
insightsSnapshotHash: "OPERATIONAL",
|
|
insightsWarmFailedAt: "OPERATIONAL",
|
|
insightsBriefingRerollDate: "OPERATIONAL",
|
|
morningDigestRefreshedOn: "OPERATIONAL",
|
|
coachLastSeenAt: "OPERATIONAL",
|
|
stravaLastActivityAt: "OPERATIONAL",
|
|
stravaBackfillCompletedAt: "OPERATIONAL",
|
|
stravaBackfillResumeBefore: "OPERATIONAL",
|
|
// The restored account's Oura backfill reads its history again from the
|
|
// provider; a carried note would claim history this host never imported.
|
|
ouraBackfillProgress: "OPERATIONAL",
|
|
ouraBackfillCompletedAt: "OPERATIONAL",
|
|
healthKitLastSyncedAt: "OPERATIONAL",
|
|
healthKitLastSyncTrigger: "OPERATIONAL",
|
|
healthKitLastBackgroundSyncAt: "OPERATIONAL",
|
|
lastSyncedAt: "OPERATIONAL",
|
|
// The restore stamps its own: paired clients' cursors from before it must
|
|
// be refused, which a carried value would undo.
|
|
syncResetAt: "OPERATIONAL",
|
|
// The air-quality history backfill's progress; the restored
|
|
// account's job recounts it on its next run.
|
|
environmentAqHistoryJson: "OPERATIONAL",
|
|
} as const satisfies Readonly<
|
|
Record<string, Exclude<UserColumnBackupClass, "SETTING">>
|
|
>;
|