fix(sync): pass correct opType from client to server in snapshot upload

The server was hardcoding opType as 'SYNC_IMPORT' for all snapshot
uploads, losing the distinction between SYNC_IMPORT, BACKUP_IMPORT,
and REPAIR operations. Now the client sends snapshotOpType in the
request body, and the server uses it (with fallback to 'SYNC_IMPORT'
for backward compatibility with legacy clients).

https://claude.ai/code/session_01XnqdpGbFR6VRPdCAbZ2EB9
This commit is contained in:
Claude 2026-02-12 09:57:08 +00:00
parent bf3d821de4
commit f328c35c4d
No known key found for this signature in database
3 changed files with 13 additions and 1 deletions

View file

@ -94,6 +94,8 @@ const UploadSnapshotSchema = z.object({
// Client's operation ID - server MUST use this to prevent ID mismatch bugs
opId: z.string().uuid().optional(),
isCleanSlate: z.boolean().optional(),
// Client-provided opType for correct operation typing (optional for backward compat)
snapshotOpType: z.enum(['SYNC_IMPORT', 'BACKUP_IMPORT', 'REPAIR']).optional(),
});
// Error helper
@ -651,6 +653,7 @@ export const syncRoutes = async (fastify: FastifyInstance): Promise<void> => {
isPayloadEncrypted,
opId,
isCleanSlate,
snapshotOpType,
} = parseResult.data;
const syncService = getSyncService();
@ -749,7 +752,10 @@ export const syncRoutes = async (fastify: FastifyInstance): Promise<void> => {
id: opId ?? uuidv7(),
clientId,
actionType: '[SP_ALL] Load(import) all data',
opType: 'SYNC_IMPORT' as const,
opType: (snapshotOpType ?? 'SYNC_IMPORT') as
| 'SYNC_IMPORT'
| 'BACKUP_IMPORT'
| 'REPAIR',
entityType: 'ALL',
payload: state,
vectorClock,

View file

@ -11,6 +11,7 @@ import {
SnapshotUploadResponse,
RestoreCapable,
RestorePoint,
RestorePointType,
RestoreSnapshotResponse,
} from '../provider.interface';
import { SyncCredentialStore } from '../credential-store.service';
@ -236,6 +237,7 @@ export class SuperSyncProvider
isPayloadEncrypted: boolean | undefined,
opId: string,
isCleanSlate?: boolean,
snapshotOpType?: RestorePointType,
): Promise<SnapshotUploadResponse> {
SyncLog.normal(this.logLabel, 'uploadSnapshot: Starting...', {
clientId,
@ -244,6 +246,7 @@ export class SuperSyncProvider
isPayloadEncrypted,
opId,
isCleanSlate,
snapshotOpType,
});
const cfg = await this._cfgOrError();
@ -257,6 +260,7 @@ export class SuperSyncProvider
isPayloadEncrypted,
opId, // CRITICAL: Server must use this ID to prevent ID mismatch bugs
isCleanSlate,
snapshotOpType,
});
// On native platforms (Android/iOS), use CapacitorHttp with base64-encoded gzip

View file

@ -14,6 +14,7 @@ import { LOCK_NAMES, MAX_OPS_PER_UPLOAD_REQUEST } from '../core/operation-log.co
import { chunkArray } from '../../util/chunk-array';
import {
OperationSyncCapable,
RestorePointType,
SyncOperation,
} from '../sync-providers/provider.interface';
import { syncOpToOperation } from './operation-sync.util';
@ -436,6 +437,7 @@ export class OperationLogUploadService {
isPayloadEncrypted,
op.id, // CRITICAL: Pass op.id to prevent ID mismatch bugs
isCleanSlate,
op.opType as RestorePointType,
);
return response;
} catch (err) {