import assert from "node:assert/strict"; import { readFileSync, readdirSync } from "node:fs"; import { join } from "node:path"; import { DatabaseSync, type SQLInputValue } from "node:sqlite"; import { describe, it } from "node:test"; import { SYNC_SNAPSHOT_CANDIDATE_UPSERT_QUERY, SYNC_SNAPSHOT_ENCRYPTION_UPSERT_QUERY, SYNC_SNAPSHOT_HEAD_INSERT_QUERY, SYNC_SNAPSHOT_HEAD_QUERY, SYNC_SNAPSHOT_HEAD_UPDATE_QUERY, } from "../src/sync_snapshot_sql.js"; import { SYNC_R2_MARK_REFERENCED_QUERY } from "../src/sync_r2_gc.js"; import { SyncSnapshotHeadSchemaError, type SyncSnapshotRow, snapshotDocumentFromRow, } from "../src/sync_snapshot_head.js"; const USER_ID = "user-01"; const DEVICE_ID = "device-01"; const KEY_ID = "1".repeat(64); const NEXT_KEY_ID = "2".repeat(64); const WRITE_TOKEN = "9".repeat(64); const MIGRATIONS_DIR = join(process.cwd(), "migrations"); interface HeadRef { revision: number; snapshotId: string; payloadHash: string; } interface Candidate { snapshotId: string; payloadHash: string; contentHash: string; logicalClock: number; headRevision: number; base: HeadRef | null; keyId?: string; generation?: number; } describe("sync snapshot head SQLite guards", () => { it("commits a genesis head through the route SQL", () => { using database = databaseWithApprovedDevice(); const genesis = candidate({ payloadHash: "a".repeat(64) }); commitCandidate(database, genesis); assert.deepEqual(currentHead(database), { head_revision: 1, snapshot_id: "device-01", payload_hash: "a".repeat(64), }); }); it("allows one writer per base even when the stale writer has a higher clock", () => { using database = databaseWithApprovedDevice(); const genesis = candidate({ payloadHash: "a".repeat(64) }); commitCandidate(database, genesis); const base = headRef(genesis); const winner = candidate({ payloadHash: "b".repeat(64), contentHash: "3".repeat(64), logicalClock: 11, headRevision: 2, base, }); commitCandidate(database, winner); const loser = candidate({ payloadHash: "c".repeat(64), contentHash: "4".repeat(64), logicalClock: 999, headRevision: 2, base, }); assert.throws( () => commitCandidate(database, loser), /sync_r2_write_fenced/, ); assert.deepEqual(currentHead(database), { head_revision: 2, snapshot_id: "device-01", payload_hash: "b".repeat(64), }); assert.deepEqual(snapshotState(database, "device-01"), { payload_hash: "b".repeat(64), content_hash: "3".repeat(64), logical_clock: 11, head_revision: 2, }); }); it("rolls back candidate metadata when the final head guard aborts", () => { using database = databaseWithApprovedDevice(); const genesis = candidate({ payloadHash: "a".repeat(64) }); commitCandidate(database, genesis); const child = candidate({ snapshotId: "device-02", payloadHash: "b".repeat(64), contentHash: "3".repeat(64), logicalClock: 11, headRevision: 2, base: headRef(genesis), }); assert.throws( () => commitCandidate(database, child, "f".repeat(64)), /sync_r2_write_fenced/, ); assert.equal(snapshotState(database, "device-02"), undefined); assert.deepEqual(currentHead(database), { head_revision: 1, snapshot_id: "device-01", payload_hash: "a".repeat(64), }); }); it("advances an old-generation base with the current rotated key", () => { using database = databaseWithApprovedDevice(); const genesis = candidate({ payloadHash: "a".repeat(64) }); commitCandidate(database, genesis); database.prepare(` UPDATE sync_vault_accounts SET current_key_id = ?, current_generation = 2, updated_at = 2 WHERE user_id = ? `).run(NEXT_KEY_ID, USER_ID); const child = candidate({ payloadHash: "b".repeat(64), contentHash: "3".repeat(64), logicalClock: 11, headRevision: 2, base: headRef(genesis), keyId: NEXT_KEY_ID, generation: 2, }); commitCandidate(database, child); assert.deepEqual(snapshotState(database, DEVICE_ID), { payload_hash: "b".repeat(64), content_hash: "3".repeat(64), logical_clock: 11, head_revision: 2, }); }); it("surfaces a current head whose encryption row is missing", () => { using database = databaseWithApprovedDevice(); const genesis = candidate({ payloadHash: "a".repeat(64) }); commitCandidate(database, genesis); const deletion = database.prepare(` DELETE FROM sync_snapshot_encryption WHERE user_id = ? AND snapshot_id = ? `); assert.throws(() => deletion.run(USER_ID, DEVICE_ID), /FOREIGN KEY constraint failed/); database.exec("PRAGMA foreign_keys = OFF"); deletion.run(USER_ID, DEVICE_ID); database.exec("PRAGMA foreign_keys = ON"); const row = database.prepare(SYNC_SNAPSHOT_HEAD_QUERY).get(USER_ID) as | SyncSnapshotRow | undefined; assert.ok(row !== undefined); assert.throws( () => snapshotDocumentFromRow(row), (error) => error instanceof SyncSnapshotHeadSchemaError && error.message === "encryption_version_invalid", ); }); }); function databaseWithApprovedDevice(): DatabaseSync { const database = new DatabaseSync(":memory:"); database.exec("PRAGMA foreign_keys = ON"); for (const fileName of readdirSync(MIGRATIONS_DIR).filter((name) => name.endsWith(".sql")).sort()) { database.exec(readFileSync(join(MIGRATIONS_DIR, fileName), "utf8")); } database.exec(` INSERT INTO better_auth_user ( id, name, email, emailVerified, createdAt, updatedAt ) VALUES ( '${USER_ID}', 'User', 'user@example.com', 1, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z' ); INSERT INTO user_devices ( user_id, device_id, public_key, device_name, platform, approval_status, created_at, approved_at, last_active_at, revoked_at, idempotency_key ) VALUES ( '${USER_ID}', '${DEVICE_ID}', '${"d".repeat(64)}', 'Mac', 'macOS', 'approved', 1, 1, 1, NULL, 'device-register-0001' ); INSERT INTO user_device_keys ( user_id, device_id, signing_public_key, wrapping_public_key, key_protocol_version, created_at ) VALUES ( '${USER_ID}', '${DEVICE_ID}', '${"d".repeat(64)}', '${"e".repeat(64)}', 2, 1 ); INSERT INTO sync_vault_accounts ( user_id, current_key_id, current_generation, created_at, updated_at ) VALUES ('${USER_ID}', '${KEY_ID}', 1, 1, 1); `); return database; } function commitCandidate( database: DatabaseSync, value: Candidate, headPayloadHash = value.payloadHash, ): void { const snapshotValues = candidateValues(value); const r2Key = candidateR2Key(value); database.prepare(` INSERT INTO sync_r2_gc_candidates ( r2_key, user_id, owner_hash, object_kind, state, write_token, lease_expires_at, gc_token, created_at, updated_at, referenced_at, ready_at, delete_started_at, deleted_at ) VALUES (?, ?, ?, 'snapshot', 'pending', ?, 1000, NULL, 0, 0, NULL, NULL, NULL, NULL) `).run(r2Key, USER_ID, "f".repeat(64), WRITE_TOKEN); database.exec("BEGIN IMMEDIATE"); try { database.prepare(SYNC_SNAPSHOT_CANDIDATE_UPSERT_QUERY).run( ...snapshotValues, value.keyId ?? KEY_ID, value.generation ?? 1, WRITE_TOKEN, value.headRevision, ); database.prepare(SYNC_SNAPSHOT_ENCRYPTION_UPSERT_QUERY).run( ...snapshotValues, 2, value.generation ?? 1, value.keyId ?? KEY_ID, value.contentHash, WRITE_TOKEN, value.headRevision, ); if (value.base === null) { database.prepare(SYNC_SNAPSHOT_HEAD_INSERT_QUERY).run( USER_ID, value.headRevision, value.snapshotId, headPayloadHash, value.headRevision, r2Key, USER_ID, WRITE_TOKEN, value.headRevision, ); } else { database.prepare(SYNC_SNAPSHOT_HEAD_UPDATE_QUERY).run( value.headRevision, value.snapshotId, headPayloadHash, value.headRevision, USER_ID, r2Key, USER_ID, WRITE_TOKEN, value.headRevision, ); } database.prepare(SYNC_R2_MARK_REFERENCED_QUERY).run( value.headRevision, value.headRevision, value.headRevision, r2Key, USER_ID, WRITE_TOKEN, value.headRevision, ); database.exec("COMMIT"); } catch (error) { database.exec("ROLLBACK"); throw error; } } function candidateValues(value: Candidate): SQLInputValue[] { return [ USER_ID, value.snapshotId, candidateR2Key(value), value.payloadHash, 1, value.logicalClock, DEVICE_ID, 26, value.headRevision, value.headRevision, value.base?.revision ?? null, value.base?.snapshotId ?? null, value.base?.payloadHash ?? null, ]; } function candidateR2Key(value: Candidate): string { return `sync-snapshots/us-east/${"f".repeat(64)}/${value.snapshotId}/${value.payloadHash}.bin`; } function candidate(overrides: Partial): Candidate { return { snapshotId: DEVICE_ID, payloadHash: "a".repeat(64), contentHash: "2".repeat(64), logicalClock: 10, headRevision: 1, base: null, ...overrides, }; } function headRef(value: Candidate): HeadRef { return { revision: value.headRevision, snapshotId: value.snapshotId, payloadHash: value.payloadHash, }; } function currentHead(database: DatabaseSync): Record | undefined { const row = database.prepare(` SELECT head_revision, snapshot_id, payload_hash FROM sync_snapshot_heads WHERE user_id = ? `).get(USER_ID) as Record | undefined; return row === undefined ? undefined : { ...row }; } function snapshotState( database: DatabaseSync, snapshotId: string, ): Record | undefined { const row = database.prepare(` SELECT snapshot.payload_hash, encryption.content_hash, snapshot.logical_clock, snapshot.head_revision FROM sync_snapshots AS snapshot INNER JOIN sync_snapshot_encryption AS encryption ON encryption.user_id = snapshot.user_id AND encryption.snapshot_id = snapshot.snapshot_id WHERE snapshot.user_id = ? AND snapshot.snapshot_id = ? `).get(USER_ID, snapshotId) as Record | undefined; return row === undefined ? undefined : { ...row }; }