mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 13:52:20 +00:00
fix(backup): refuse a stored copy that will not open with 422
A `DataBackup` row that fails to decrypt answered 500 on the restore, the download and the preview. It is not a server fault: either the key that wrote the copy is no longer in `ENCRYPTION_KEYS` — a rotation that dropped the legacy entry before the script reported zero rows, the case the rotation runbook warns about — or the stored bytes are not the ones that were written. Every other bad-input arm on those routes already answers 4xx, and the 500 also paged the error reporter as if the process had broken. All three now answer 422 with `meta.errorCode = backup.payload.undecryptable` and one shared sentence, so the three routes cannot drift into three wordings for one condition. The download's second 500 stays: a payload this instance wrote and cannot parse really is a fault here. The two routes that open a stored copy move from the unpublished list into the contract, because the refusal is now a promise an operator can look up rather than whatever the handler happened to answer. The restore publishes its `Idempotency-Key` header with them.
This commit is contained in:
1 parent
6b366b4f27
commit
d3f087475c
11 files changed
+471
-27
No files matched your search
@@ -223,6 +223,28 @@ export async function packBackupBlobStreaming(
|
||||
return pieces.join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* The refusal a caller gets when a stored copy will not open, as one string.
|
||||
*
|
||||
* Two causes, one answer, because the envelope cannot tell them apart and the
|
||||
* operator's next step is the same either way: the key that wrote the copy is
|
||||
* no longer in `ENCRYPTION_KEYS` (a rotation that dropped the legacy entry too
|
||||
* early — the case `docs/ops/encryption-key-rotation.md` warns about), or the
|
||||
* stored bytes are not the bytes that were written. Neither is a server fault,
|
||||
* so neither is a 500: it is bad stored input, refused with 422 like every
|
||||
* other bad input on these routes, and it never reaches the error reporter as
|
||||
* if the process had broken.
|
||||
*
|
||||
* Shared rather than retyped, so the three routes that decrypt a stored copy —
|
||||
* restore, download, summary — cannot drift into three different sentences for
|
||||
* the same condition.
|
||||
*/
|
||||
export const BACKUP_UNDECRYPTABLE_ERROR =
|
||||
"Backup payload could not be decrypted — either the key that wrote this copy is no longer in ENCRYPTION_KEYS, or the stored copy is not the one that was written. Nothing was changed.";
|
||||
|
||||
/** The stable machine-readable half of {@link BACKUP_UNDECRYPTABLE_ERROR}. */
|
||||
export const BACKUP_UNDECRYPTABLE_CODE = "backup.payload.undecryptable";
|
||||
|
||||
/**
|
||||
* A stored `DataBackup.data` string → the backup JSON.
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user