fix(deploy): verify retired staging migration
This commit is contained in:
@@ -6,6 +6,12 @@ import pg from "pg";
|
||||
|
||||
const { Client } = pg;
|
||||
const migrationFilenamePattern = /^\d{14}_[a-z0-9_]+\.sql$/;
|
||||
const retiredMigrationChecksums = new Map([
|
||||
[
|
||||
"20260727010000_admin_users.sql",
|
||||
"785f4fdc65db1028623cc7b5a2571217b913ef9e55f5a17b01658a71612976de",
|
||||
],
|
||||
]);
|
||||
|
||||
class SafeMigrationError extends Error {}
|
||||
|
||||
@@ -81,17 +87,22 @@ async function readLedger(client) {
|
||||
return new Map(result.rows.map((row) => [row.filename, row.checksum]));
|
||||
}
|
||||
|
||||
function assertLedgerFilesPresent(ledger, files) {
|
||||
export function assertLedgerFilesPresent(ledger, files) {
|
||||
const reviewedFilenames = new Set(files.map((file) => file.filename));
|
||||
for (const filename of ledger.keys()) {
|
||||
if (!reviewedFilenames.has(filename)) {
|
||||
if (!migrationFilenamePattern.test(filename)) {
|
||||
throw new SafeMigrationError(
|
||||
"migration ledger contains an invalid filename",
|
||||
);
|
||||
}
|
||||
for (const [filename, recordedChecksum] of ledger) {
|
||||
if (reviewedFilenames.has(filename)) continue;
|
||||
if (!migrationFilenamePattern.test(filename)) {
|
||||
throw new SafeMigrationError(
|
||||
"migration ledger contains an invalid filename",
|
||||
);
|
||||
}
|
||||
const retiredChecksum = retiredMigrationChecksums.get(filename);
|
||||
if (retiredChecksum === undefined) {
|
||||
throw new SafeMigrationError(`migration file missing: ${filename}`);
|
||||
}
|
||||
if (recordedChecksum !== retiredChecksum) {
|
||||
throw new SafeMigrationError(`migration checksum mismatch: ${filename}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { assertLedgerFilesPresent } from "../scripts/db-migrate.mjs";
|
||||
|
||||
const retiredFilename = "20260727010000_admin_users.sql";
|
||||
const retiredChecksum =
|
||||
"785f4fdc65db1028623cc7b5a2571217b913ef9e55f5a17b01658a71612976de";
|
||||
|
||||
test("accepts the retired staging migration only with its reviewed checksum", () => {
|
||||
assert.doesNotThrow(() =>
|
||||
assertLedgerFilesPresent(new Map([[retiredFilename, retiredChecksum]]), []),
|
||||
);
|
||||
});
|
||||
|
||||
test("rejects checksum drift for the retired staging migration", () => {
|
||||
assert.throws(
|
||||
() => assertLedgerFilesPresent(new Map([[retiredFilename, "0".repeat(64)]]), []),
|
||||
/migration checksum mismatch: 20260727010000_admin_users\.sql/,
|
||||
);
|
||||
});
|
||||
|
||||
test("still rejects any undeclared missing migration", () => {
|
||||
assert.throws(
|
||||
() =>
|
||||
assertLedgerFilesPresent(
|
||||
new Map([["20260727020000_unknown.sql", "0".repeat(64)]]),
|
||||
[],
|
||||
),
|
||||
/migration file missing: 20260727020000_unknown\.sql/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user