fix(backup): check the weekly copy's preview against the schema section by section

Taking the weekly preview from the writer dropped the schema check the
old scanner ran at the end, so a copy that violates the schema was
described as restorable. The collector now checks each section in its
JSON form as the writer shows it, an array section and a bulk table one
element at a time, plus the schema's one whole-document rule (a
canonical copy names every measurement by id). A copy that fails gets
no stored preview, and the preview route reads the copy itself, as it
did before. Nothing is kept beyond one element.

Measured on the preview memory test under --max-old-space-size=524:
held after the producer 3 MB with and without the check, raw peak
heapUsed 293 MB with it (335 MB without). The test now loads the
backup schema before its baseline: a module loaded once per process,
not memory that grows with the record.
This commit is contained in:
Marc-André Bombeck committed 2026-10-04 22:17:27 +02:00
1 parent 48ed149ef0
commit 4769502a4b
5 files changed
+133 -14

No files matched your search

@@ -118,6 +118,11 @@ describe("weekly copy preview under a memory budget", () => {
});
it("holds nothing of the record once the producer is done", async () => {
// The backup schema the preview checks each section against is a module
// loaded once per process (about 12 MB, `backup-summary.ts`), not memory
// that grows with the record: load it before the baseline, as a running
// server has long since done.
await import("@/lib/validations/backup");
const baseline = liveHeapBytes();
let peakHeld = 0;
let heldAtEnd = 0;
@@ -127,7 +132,7 @@ describe("weekly copy preview under a memory budget", () => {
rawPeak = Math.max(rawPeak, process.memoryUsage().heapUsed);
}, 5);
// Wired exactly as the weekly job wires it (`backup-handlers.ts`).
const preview = createBackupPreviewCollector();
const preview = await createBackupPreviewCollector();
let counts: FullBackupCounts | undefined;
try {
await storeBackupBlob(
+2 -2
View File
@@ -1495,7 +1495,7 @@ describe("every model the plan claims two-ended survives a real restore", () =>
// below then covers BOTH, since the row that gets restored comes from the
// streamed bytes.
let streamedJson = "";
const previewCollector = createBackupPreviewCollector();
const previewCollector = await createBackupPreviewCollector();
const streamedCounts = await streamFullBackupJson(
prisma,
OWNER_ID,
@@ -3779,7 +3779,7 @@ describe("every column of every two-ended model survives a real restore", () =>
const exportedAt = new Date("2026-08-01T00:00:00.000Z");
let json = "";
const previewCollector = createBackupPreviewCollector();
const previewCollector = await createBackupPreviewCollector();
const counts = await streamFullBackupJson(
prisma,
OWNER_ID,