Skip to content

Commit 92bf8c5

Browse files
dmealingclaude
andcommitted
feat(migrate-ts): ledger baseline marker (recordBaseline / baselineRecord)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 75a8abd commit 92bf8c5

2 files changed

Lines changed: 65 additions & 0 deletions

File tree

server/typescript/packages/migrate-ts/src/apply/ledger.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,42 @@ export async function appliedRecords(
193193
}
194194
return map;
195195
}
196+
197+
/** Reserved ledger name for the baseline marker (sorts before any timestamped migration). */
198+
export const BASELINE_NAME = "0000-baseline";
199+
200+
/**
201+
* Record (or overwrite) the baseline marker — the snapshot checksum captured when
202+
* `migrate baseline` seeded the reference snapshot. Lets a later check detect a
203+
* snapshot that was hand-edited out of sync with the migration chain.
204+
*/
205+
export async function recordBaseline(
206+
db: Kysely<Record<string, unknown>>,
207+
dialect: LedgerDialect = "sqlite",
208+
checksum: string,
209+
opts?: LedgerOptions,
210+
): Promise<void> {
211+
const ledger = resolveLedger(dialect, opts);
212+
await ensureLedger(db, dialect, opts);
213+
const appliedAt = new Date().toISOString();
214+
// Upsert: delete any prior baseline, then insert (portable across sqlite/pg).
215+
await sql`DELETE FROM ${ledger.ref} WHERE name = ${BASELINE_NAME}`.execute(db);
216+
await sql`
217+
INSERT INTO ${ledger.ref} (name, applied_at, checksum)
218+
VALUES (${BASELINE_NAME}, ${appliedAt}, ${checksum})
219+
`.execute(db);
220+
}
221+
222+
/** Read the baseline marker, or null if none recorded. */
223+
export async function baselineRecord(
224+
db: Kysely<Record<string, unknown>>,
225+
dialect: LedgerDialect = "sqlite",
226+
opts?: LedgerOptions,
227+
): Promise<{ name: string; checksum: string } | null> {
228+
const ledger = resolveLedger(dialect, opts);
229+
const result = await sql<{ name: string; checksum: string }>`
230+
SELECT name, checksum FROM ${ledger.ref} WHERE name = ${BASELINE_NAME}
231+
`.execute(db);
232+
const row = result.rows[0];
233+
return row ? { name: row.name, checksum: row.checksum } : null;
234+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// test/integrity/baseline-marker.test.ts
2+
import { describe, test, expect, afterAll } from "bun:test";
3+
import { mkdtemp, rm } from "node:fs/promises";
4+
import { tmpdir } from "node:os";
5+
import { join } from "node:path";
6+
import { Kysely } from "kysely";
7+
import { LibsqlDialect } from "@libsql/kysely-libsql";
8+
import { ensureLedger, recordBaseline, baselineRecord } from "../../src/apply/ledger.js";
9+
10+
const tmps: string[] = [];
11+
function db(file: string) { return new Kysely<Record<string, unknown>>({ dialect: new LibsqlDialect({ url: `file:${file}` }) }); }
12+
async function root() { const d = await mkdtemp(join(tmpdir(), "baseline-")); tmps.push(d); return d; }
13+
afterAll(async () => { for (const d of tmps) await rm(d, { recursive: true, force: true }); });
14+
15+
describe("ledger baseline marker", () => {
16+
test("recordBaseline stores the checksum; baselineRecord reads it back", async () => {
17+
const k = db(join(await root(), "b.db"));
18+
try {
19+
await ensureLedger(k, "sqlite");
20+
expect(await baselineRecord(k, "sqlite")).toBeNull();
21+
await recordBaseline(k, "sqlite", "abc123checksum");
22+
const rec = await baselineRecord(k, "sqlite");
23+
expect(rec?.checksum).toBe("abc123checksum");
24+
} finally { await k.destroy(); }
25+
});
26+
});

0 commit comments

Comments
 (0)