From e9f8b1eb43c5aead325064ad1eca0b1e8f6b74e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 8 May 2026 19:02:21 -0400 Subject: [PATCH] Expose sync status API --- cloudflare/src/index.ts | 139 +--------- cloudflare/src/sync_routes.ts | 182 +++++++++++++ cloudflare/src/sync_status.ts | 270 ++++++++++++++++++++ cloudflare/tests/index.test.ts | 2 +- cloudflare/tests/sync_status_routes.test.ts | 195 ++++++++++++++ 5 files changed, 653 insertions(+), 135 deletions(-) create mode 100644 cloudflare/src/sync_routes.ts create mode 100644 cloudflare/src/sync_status.ts create mode 100644 cloudflare/tests/sync_status_routes.test.ts diff --git a/cloudflare/src/index.ts b/cloudflare/src/index.ts index f79ee73..60c255a 100644 --- a/cloudflare/src/index.ts +++ b/cloudflare/src/index.ts @@ -1,9 +1,5 @@ import type { Env } from "./bindings.js"; -import { - withApprovedDeviceApiControls, - withAuthenticatedApiControls, - withPublicApiControls, -} from "./api_controls.js"; +import { withAuthenticatedApiControls, withPublicApiControls } from "./api_controls.js"; import { DevicePermissionError, DevicePersistenceError, @@ -35,21 +31,7 @@ import { parsePublicSigningKeysDocument, publicSigningKeysKvKey, } from "./signing_keys.js"; -import { - SyncPushConflictError, - SyncPushPersistenceError, - SyncPushRequestError, - syncPushDocument, -} from "./sync_push.js"; -import { - SyncSnapshotConflictError, - SyncSnapshotNotFoundError, - SyncSnapshotPersistenceError, - SyncSnapshotRequestError, - syncSnapshotDownloadDocument, - syncSnapshotUploadDocument, -} from "./sync_snapshot.js"; -import { SyncRequestError, SyncSchemaError, syncPullDocument } from "./sync_pull.js"; +import { handleSyncRoute } from "./sync_routes.js"; import { jsonResponse } from "./responses.js"; export default { @@ -188,120 +170,9 @@ export async function handleRequest(request: Request, env: Env): Promise { - try { - return jsonResponse(await syncPullDocument(url, env, context), 200, { - "Cache-Control": "no-store", - }); - } catch (error) { - if (error instanceof SyncRequestError) { - return jsonResponse( - { error: "invalid_sync_pull" }, - 400, - { "Cache-Control": "no-store" }, - ); - } - if (error instanceof SyncSchemaError) { - return jsonResponse( - { error: "sync_pull_invalid" }, - 500, - { "Cache-Control": "no-store" }, - ); - } - throw error; - } - }, - ); - } - if (url.pathname === "/api/sync/push") { - return withApprovedDeviceApiControls( - request, - env, - "sync.push", - ["POST"], - async (context) => { - try { - return jsonResponse(await syncPushDocument(request, env, context), 201, { - "Cache-Control": "no-store", - }); - } catch (error) { - if (error instanceof SyncPushRequestError) { - return jsonResponse( - { error: "invalid_sync_push" }, - 400, - { "Cache-Control": "no-store" }, - ); - } - if (error instanceof SyncPushConflictError) { - return jsonResponse({ error: "sync_conflict" }, 409, { "Cache-Control": "no-store" }); - } - if (error instanceof SyncPushPersistenceError) { - return jsonResponse( - { error: "sync_push_failed" }, - 500, - { "Cache-Control": "no-store" }, - ); - } - throw error; - } - }, - ); - } - if (url.pathname === "/api/sync/snapshot") { - return withApprovedDeviceApiControls( - request, - env, - "sync.snapshot", - ["GET", "POST"], - async (context) => { - try { - if (request.method === "POST") { - return jsonResponse(await syncSnapshotUploadDocument(request, env, context), 201, { - "Cache-Control": "no-store", - }); - } - return jsonResponse(await syncSnapshotDownloadDocument(url, env, context), 200, { - "Cache-Control": "no-store", - }); - } catch (error) { - if (error instanceof SyncSnapshotRequestError) { - return jsonResponse( - { error: "invalid_sync_snapshot" }, - 400, - { "Cache-Control": "no-store" }, - ); - } - if (error instanceof SyncSnapshotNotFoundError) { - return jsonResponse( - { error: "sync_snapshot_not_found" }, - 404, - { "Cache-Control": "no-store" }, - ); - } - if (error instanceof SyncSnapshotConflictError) { - return jsonResponse( - { error: "sync_snapshot_conflict" }, - 409, - { "Cache-Control": "no-store" }, - ); - } - if (error instanceof SyncSnapshotPersistenceError) { - return jsonResponse( - { error: "sync_snapshot_failed" }, - 500, - { "Cache-Control": "no-store" }, - ); - } - throw error; - } - }, - ); + const syncResponse = await handleSyncRoute(request, env, url); + if (syncResponse !== null) { + return syncResponse; } if (url.pathname === "/api/plugins/signing-keys") { return withPublicApiControls(request, env, "plugins.signing_keys", ["GET"], () => diff --git a/cloudflare/src/sync_routes.ts b/cloudflare/src/sync_routes.ts new file mode 100644 index 0000000..cd2da43 --- /dev/null +++ b/cloudflare/src/sync_routes.ts @@ -0,0 +1,182 @@ +import type { Env } from "./bindings.js"; +import { withApprovedDeviceApiControls } from "./api_controls.js"; +import { jsonResponse } from "./responses.js"; +import { SyncRequestError, SyncSchemaError, syncPullDocument } from "./sync_pull.js"; +import { + SyncPushConflictError, + SyncPushPersistenceError, + SyncPushRequestError, + syncPushDocument, +} from "./sync_push.js"; +import { + SyncSnapshotConflictError, + SyncSnapshotNotFoundError, + SyncSnapshotPersistenceError, + SyncSnapshotRequestError, + syncSnapshotDownloadDocument, + syncSnapshotUploadDocument, +} from "./sync_snapshot.js"; +import { SyncStatusSchemaError, syncStatusDocument } from "./sync_status.js"; + +export async function handleSyncRoute( + request: Request, + env: Env, + url: URL, +): Promise { + if (url.pathname === "/api/sync/pull") { + return handleSyncPull(request, env, url); + } + if (url.pathname === "/api/sync/push") { + return handleSyncPush(request, env); + } + if (url.pathname === "/api/sync/snapshot") { + return handleSyncSnapshot(request, env, url); + } + if (url.pathname === "/api/sync/status") { + return handleSyncStatus(request, env); + } + return null; +} + +function handleSyncPull(request: Request, env: Env, url: URL): Promise { + return withApprovedDeviceApiControls( + request, + env, + "sync.pull", + ["GET"], + async (context) => { + try { + return jsonResponse(await syncPullDocument(url, env, context), 200, { + "Cache-Control": "no-store", + }); + } catch (error) { + if (error instanceof SyncRequestError) { + return jsonResponse( + { error: "invalid_sync_pull" }, + 400, + { "Cache-Control": "no-store" }, + ); + } + if (error instanceof SyncSchemaError) { + return jsonResponse( + { error: "sync_pull_invalid" }, + 500, + { "Cache-Control": "no-store" }, + ); + } + throw error; + } + }, + ); +} + +function handleSyncPush(request: Request, env: Env): Promise { + return withApprovedDeviceApiControls( + request, + env, + "sync.push", + ["POST"], + async (context) => { + try { + return jsonResponse(await syncPushDocument(request, env, context), 201, { + "Cache-Control": "no-store", + }); + } catch (error) { + if (error instanceof SyncPushRequestError) { + return jsonResponse( + { error: "invalid_sync_push" }, + 400, + { "Cache-Control": "no-store" }, + ); + } + if (error instanceof SyncPushConflictError) { + return jsonResponse({ error: "sync_conflict" }, 409, { "Cache-Control": "no-store" }); + } + if (error instanceof SyncPushPersistenceError) { + return jsonResponse( + { error: "sync_push_failed" }, + 500, + { "Cache-Control": "no-store" }, + ); + } + throw error; + } + }, + ); +} + +function handleSyncSnapshot(request: Request, env: Env, url: URL): Promise { + return withApprovedDeviceApiControls( + request, + env, + "sync.snapshot", + ["GET", "POST"], + async (context) => { + try { + if (request.method === "POST") { + return jsonResponse(await syncSnapshotUploadDocument(request, env, context), 201, { + "Cache-Control": "no-store", + }); + } + return jsonResponse(await syncSnapshotDownloadDocument(url, env, context), 200, { + "Cache-Control": "no-store", + }); + } catch (error) { + if (error instanceof SyncSnapshotRequestError) { + return jsonResponse( + { error: "invalid_sync_snapshot" }, + 400, + { "Cache-Control": "no-store" }, + ); + } + if (error instanceof SyncSnapshotNotFoundError) { + return jsonResponse( + { error: "sync_snapshot_not_found" }, + 404, + { "Cache-Control": "no-store" }, + ); + } + if (error instanceof SyncSnapshotConflictError) { + return jsonResponse( + { error: "sync_snapshot_conflict" }, + 409, + { "Cache-Control": "no-store" }, + ); + } + if (error instanceof SyncSnapshotPersistenceError) { + return jsonResponse( + { error: "sync_snapshot_failed" }, + 500, + { "Cache-Control": "no-store" }, + ); + } + throw error; + } + }, + ); +} + +function handleSyncStatus(request: Request, env: Env): Promise { + return withApprovedDeviceApiControls( + request, + env, + "sync.status", + ["GET"], + async (context) => { + try { + return jsonResponse(await syncStatusDocument(env, context), 200, { + "Cache-Control": "no-store", + }); + } catch (error) { + if (error instanceof SyncStatusSchemaError) { + return jsonResponse( + { error: "sync_status_invalid" }, + 500, + { "Cache-Control": "no-store" }, + ); + } + throw error; + } + }, + ); +} diff --git a/cloudflare/src/sync_status.ts b/cloudflare/src/sync_status.ts new file mode 100644 index 0000000..cb2adbf --- /dev/null +++ b/cloudflare/src/sync_status.ts @@ -0,0 +1,270 @@ +import type { AuthContext } from "./auth.js"; +import type { Env } from "./bindings.js"; +import { StorageObjectError, assertSyncObjectType } from "./storage.js"; + +const CHANGE_CURSOR_QUERY = ` + SELECT + COALESCE(MAX(change_id), 0) AS latest_change_id, + COUNT(*) AS total_changes + FROM sync_change_log + WHERE user_id = ? +`; +const OBJECT_STATUS_QUERY = ` + SELECT + object_type, + SUM(CASE WHEN deleted_at IS NULL THEN 1 ELSE 0 END) AS active_count, + SUM(CASE WHEN deleted_at IS NOT NULL THEN 1 ELSE 0 END) AS deleted_count, + COALESCE(MAX(logical_clock), 0) AS latest_logical_clock, + COALESCE(MAX(updated_at), 0) AS latest_updated_at + FROM sync_objects + WHERE user_id = ? + GROUP BY object_type + ORDER BY object_type ASC +`; +const SNAPSHOT_COUNT_QUERY = ` + SELECT COUNT(*) AS total_snapshots + FROM sync_snapshots + WHERE user_id = ? +`; +const LATEST_SNAPSHOT_QUERY = ` + SELECT + snapshot_id, + payload_hash, + logical_clock, + device_id, + size_bytes, + created_at + FROM sync_snapshots + WHERE user_id = ? + ORDER BY created_at DESC, snapshot_id ASC + LIMIT 1 +`; +const APPROVED_DEVICE_COUNT_QUERY = ` + SELECT COUNT(*) AS approved_devices + FROM user_devices + WHERE user_id = ? AND approval_status = 'approved' AND revoked_at IS NULL +`; + +const SNAPSHOT_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,127}$/; +const DEVICE_ID_PATTERN = /^[a-zA-Z0-9._:-]{3,128}$/; +const SHA256_HEX = /^[a-f0-9]{64}$/; + +export interface SyncStatusDocument { + version: 1; + user_id: string; + device_id: string; + cursor: SyncCursorStatusDocument; + objects: SyncObjectStatusDocument[]; + snapshots: SyncSnapshotStatusDocument; + devices: SyncDeviceStatusDocument; +} + +export interface SyncCursorStatusDocument { + latest_change_id: number; + total_changes: number; +} + +export interface SyncObjectStatusDocument { + object_type: string; + active_count: number; + deleted_count: number; + latest_logical_clock: number; + latest_updated_at: number; +} + +export interface SyncSnapshotStatusDocument { + total_snapshots: number; + latest: SyncLatestSnapshotDocument | null; +} + +export interface SyncLatestSnapshotDocument { + snapshot_id: string; + payload_hash: string; + logical_clock: number; + device_id: string; + size_bytes: number; + created_at: number; +} + +export interface SyncDeviceStatusDocument { + approved_count: number; + current_device_id: string; + current_device_approved: true; +} + +interface ChangeCursorRow { + latest_change_id: unknown; + total_changes: unknown; +} + +interface ObjectStatusRow { + object_type: unknown; + active_count: unknown; + deleted_count: unknown; + latest_logical_clock: unknown; + latest_updated_at: unknown; +} + +interface SnapshotCountRow { + total_snapshots: unknown; +} + +interface LatestSnapshotRow { + snapshot_id: unknown; + payload_hash: unknown; + logical_clock: unknown; + device_id: unknown; + size_bytes: unknown; + created_at: unknown; +} + +interface DeviceStatusRow { + approved_devices: unknown; +} + +export class SyncStatusSchemaError extends Error { + constructor(message: string) { + super(message); + this.name = "SyncStatusSchemaError"; + } +} + +export async function syncStatusDocument( + env: Env, + context: AuthContext, +): Promise { + const deviceId = currentDeviceId(context); + const cursorRow = await env.ELY_DB.prepare(CHANGE_CURSOR_QUERY) + .bind(context.userId) + .first(); + const objectRows = await env.ELY_DB.prepare(OBJECT_STATUS_QUERY) + .bind(context.userId) + .all(); + const snapshotCountRow = await env.ELY_DB.prepare(SNAPSHOT_COUNT_QUERY) + .bind(context.userId) + .first(); + const latestSnapshotRow = await env.ELY_DB.prepare(LATEST_SNAPSHOT_QUERY) + .bind(context.userId) + .first(); + const deviceStatusRow = await env.ELY_DB.prepare(APPROVED_DEVICE_COUNT_QUERY) + .bind(context.userId) + .first(); + + return { + version: 1, + user_id: context.userId, + device_id: deviceId, + cursor: cursorStatus(cursorRow), + objects: objectRows.results.map(objectStatus), + snapshots: snapshotStatus(snapshotCountRow, latestSnapshotRow), + devices: deviceStatus(deviceStatusRow, deviceId), + }; +} + +function cursorStatus(row: ChangeCursorRow | null): SyncCursorStatusDocument { + if (row === null) { + throw new SyncStatusSchemaError("sync_cursor_status_missing"); + } + return { + latest_change_id: integer(row.latest_change_id, "latest_change_id"), + total_changes: integer(row.total_changes, "total_changes"), + }; +} + +function objectStatus(row: ObjectStatusRow): SyncObjectStatusDocument { + return { + object_type: objectType(row.object_type), + active_count: integer(row.active_count, "active_count"), + deleted_count: integer(row.deleted_count, "deleted_count"), + latest_logical_clock: integer(row.latest_logical_clock, "latest_logical_clock"), + latest_updated_at: integer(row.latest_updated_at, "latest_updated_at"), + }; +} + +function snapshotStatus( + countRow: SnapshotCountRow | null, + latestRow: LatestSnapshotRow | null, +): SyncSnapshotStatusDocument { + if (countRow === null) { + throw new SyncStatusSchemaError("sync_snapshot_status_missing"); + } + return { + total_snapshots: integer(countRow.total_snapshots, "total_snapshots"), + latest: latestRow === null ? null : latestSnapshot(latestRow), + }; +} + +function latestSnapshot(row: LatestSnapshotRow): SyncLatestSnapshotDocument { + return { + snapshot_id: snapshotId(row.snapshot_id), + payload_hash: payloadHash(row.payload_hash), + logical_clock: integer(row.logical_clock, "logical_clock"), + device_id: deviceId(row.device_id), + size_bytes: integer(row.size_bytes, "size_bytes"), + created_at: integer(row.created_at, "created_at"), + }; +} + +function deviceStatus( + row: DeviceStatusRow | null, + currentDeviceId: string, +): SyncDeviceStatusDocument { + if (row === null) { + throw new SyncStatusSchemaError("sync_device_status_missing"); + } + return { + approved_count: integer(row.approved_devices, "approved_devices"), + current_device_id: currentDeviceId, + current_device_approved: true, + }; +} + +function currentDeviceId(context: AuthContext): string { + if (context.deviceId === undefined) { + throw new SyncStatusSchemaError("device_context_required"); + } + return context.deviceId; +} + +function objectType(value: unknown): string { + if (typeof value !== "string") { + throw new SyncStatusSchemaError("object_type_invalid"); + } + try { + assertSyncObjectType(value); + } catch (error) { + if (error instanceof StorageObjectError) { + throw new SyncStatusSchemaError(error.message); + } + throw error; + } + return value; +} + +function snapshotId(value: unknown): string { + if (typeof value !== "string" || !SNAPSHOT_ID_PATTERN.test(value)) { + throw new SyncStatusSchemaError("snapshot_id_invalid"); + } + return value; +} + +function deviceId(value: unknown): string { + if (typeof value !== "string" || !DEVICE_ID_PATTERN.test(value)) { + throw new SyncStatusSchemaError("device_id_invalid"); + } + return value; +} + +function payloadHash(value: unknown): string { + if (typeof value !== "string" || !SHA256_HEX.test(value)) { + throw new SyncStatusSchemaError("payload_hash_invalid"); + } + return value; +} + +function integer(value: unknown, label: string): number { + if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) { + throw new SyncStatusSchemaError(`${label}_invalid`); + } + return value; +} diff --git a/cloudflare/tests/index.test.ts b/cloudflare/tests/index.test.ts index b790e72..c39ab67 100644 --- a/cloudflare/tests/index.test.ts +++ b/cloudflare/tests/index.test.ts @@ -74,7 +74,7 @@ describe("worker routes", () => { it("returns JSON not found for unknown routes", async () => { const response = await handleRequest( - new Request("https://elydora.test/api/sync/status"), + new Request("https://elydora.test/api/unknown"), testEnv(null), ); diff --git a/cloudflare/tests/sync_status_routes.test.ts b/cloudflare/tests/sync_status_routes.test.ts new file mode 100644 index 0000000..5ed03aa --- /dev/null +++ b/cloudflare/tests/sync_status_routes.test.ts @@ -0,0 +1,195 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; + +import { authSessionCacheKvKey, authTokenHash } from "../src/auth.js"; +import { handleRequest } from "../src/index.js"; +import { ACCESS_TOKEN, sessionDocument, testD1Database, testEnv } from "./devices_test_support.js"; + +const USER_ID = "user-01"; +const DEVICE_ID = "device-01"; + +describe("sync status routes", () => { + it("returns cloud sync cursor, object, snapshot, and device status", async () => { + const tokenHash = await authTokenHash(ACCESS_TOKEN); + const d1 = testD1Database({ + firstRows: [ + { device_id: DEVICE_ID }, + { latest_change_id: 51, total_changes: 7 }, + { total_snapshots: 2 }, + latestSnapshotRow(), + { approved_devices: 3 }, + ], + allRows: [ + objectStatusRow({ object_type: "bookmarks", active_count: 4, deleted_count: 1 }), + objectStatusRow({ object_type: "tabs", active_count: 9, latest_logical_clock: 44 }), + ], + }); + + const response = await handleRequest( + syncStatusRequest(), + testEnv({ + d1, + kvEntries: [[authSessionCacheKvKey("local", tokenHash), sessionDocument(DEVICE_ID)]], + }), + ); + + assert.equal(response.status, 200); + assert.equal(response.headers.get("cache-control"), "no-store"); + assert.deepEqual(await response.json(), { + version: 1, + user_id: USER_ID, + device_id: DEVICE_ID, + cursor: { latest_change_id: 51, total_changes: 7 }, + objects: [ + objectStatusDocument({ object_type: "bookmarks", active_count: 4, deleted_count: 1 }), + objectStatusDocument({ object_type: "tabs", active_count: 9, latest_logical_clock: 44 }), + ], + snapshots: { + total_snapshots: 2, + latest: latestSnapshotRow(), + }, + devices: { + approved_count: 3, + current_device_id: DEVICE_ID, + current_device_approved: true, + }, + }); + assert.ok(d1.queries[0]?.includes("approval_status = 'approved'")); + assert.ok(d1.queries[1]?.includes("FROM sync_change_log")); + assert.ok(d1.queries[2]?.includes("FROM sync_objects")); + assert.ok(d1.queries[3]?.includes("FROM sync_snapshots")); + assert.ok(d1.queries[4]?.includes("FROM sync_snapshots")); + assert.ok(d1.queries[5]?.includes("FROM user_devices")); + assert.deepEqual(d1.binds, [ + [USER_ID, DEVICE_ID], + [USER_ID], + [USER_ID], + [USER_ID], + [USER_ID], + [USER_ID], + ]); + }); + + it("returns empty status when the account has no sync facts", async () => { + const tokenHash = await authTokenHash(ACCESS_TOKEN); + const d1 = testD1Database({ + firstRows: [ + { device_id: DEVICE_ID }, + { latest_change_id: 0, total_changes: 0 }, + { total_snapshots: 0 }, + null, + { approved_devices: 1 }, + ], + }); + + const response = await handleRequest( + syncStatusRequest(), + testEnv({ + d1, + kvEntries: [[authSessionCacheKvKey("local", tokenHash), sessionDocument(DEVICE_ID)]], + }), + ); + + assert.equal(response.status, 200); + const body = (await response.json()) as { + cursor: { latest_change_id: number; total_changes: number }; + objects: []; + snapshots: { total_snapshots: number; latest: null }; + }; + assert.deepEqual(body.cursor, { latest_change_id: 0, total_changes: 0 }); + assert.deepEqual(body.objects, []); + assert.deepEqual(body.snapshots, { total_snapshots: 0, latest: null }); + }); + + it("rejects revoked devices before reading sync status", async () => { + const tokenHash = await authTokenHash(ACCESS_TOKEN); + const d1 = testD1Database({ + firstRows: [null, { latest_change_id: 51, total_changes: 7 }], + allRows: [objectStatusRow()], + }); + + const response = await handleRequest( + syncStatusRequest(), + testEnv({ + d1, + kvEntries: [[authSessionCacheKvKey("local", tokenHash), sessionDocument(DEVICE_ID)]], + }), + ); + + assert.equal(response.status, 403); + assert.deepEqual(await response.json(), { error: "device_not_approved" }); + assert.equal(d1.queries.length, 1); + }); + + it("rejects unsupported methods before session reads", async () => { + const response = await handleRequest( + new Request("https://elydora.test/api/sync/status", { + method: "POST", + headers: { authorization: `Bearer ${ACCESS_TOKEN}` }, + }), + testEnv({ d1: testD1Database([]) }), + ); + + assert.equal(response.status, 405); + assert.equal(response.headers.get("allow"), "GET"); + assert.deepEqual(await response.json(), { error: "method_not_allowed" }); + }); + + it("returns a server error for malformed status rows", async () => { + const tokenHash = await authTokenHash(ACCESS_TOKEN); + const d1 = testD1Database({ + firstRows: [ + { device_id: DEVICE_ID }, + { latest_change_id: 51, total_changes: 7 }, + { total_snapshots: 1 }, + latestSnapshotRow(), + { approved_devices: 1 }, + ], + allRows: [objectStatusRow({ object_type: "passwords" })], + }); + + const response = await handleRequest( + syncStatusRequest(), + testEnv({ + d1, + kvEntries: [[authSessionCacheKvKey("local", tokenHash), sessionDocument(DEVICE_ID)]], + }), + ); + + assert.equal(response.status, 500); + assert.deepEqual(await response.json(), { error: "sync_status_invalid" }); + }); +}); + +function syncStatusRequest(): Request { + return new Request("https://elydora.test/api/sync/status", { + headers: { authorization: `Bearer ${ACCESS_TOKEN}` }, + }); +} + +function objectStatusRow(overrides: Record = {}): Record { + return { + object_type: "tabs", + active_count: 3, + deleted_count: 0, + latest_logical_clock: 42, + latest_updated_at: 1_780_000_700, + ...overrides, + }; +} + +function objectStatusDocument(overrides: Record = {}): Record { + return objectStatusRow(overrides); +} + +function latestSnapshotRow(overrides: Record = {}): Record { + return { + snapshot_id: "snapshot-01", + payload_hash: "a".repeat(64), + logical_clock: 42, + device_id: DEVICE_ID, + size_bytes: 26, + created_at: 1_780_000_900, + ...overrides, + }; +}