Diseño del almacenamiento y la sincronización en la nube
Estado: los pasos 1-4 están implementados a fecha de 2026-09-17; el paso 5 cuenta con una referencia reproducible. Los comprobantes duraderos de las colas de salida/entrada, los marcadores de cambios, una sesión de sincronización local al proceso y un ejecutor independiente del proveedor con inyección de fallos de MemoryTransport están implementados en crates/core/src/sync.rs. Los adaptadores de archivos de OneDrive/Google Drive, la renovación de tokens y cloud sync de la CLI están implementados y probados con fixtures locales. No se han validado cuentas reales de los proveedores. El inicio de sesión móvil con AppAuth y los controles de sincronización en primer plano están implementados; el consentimiento nativo requiere registros de plataforma. Consulte cloud-authorization.md para conocer el alcance actual de la autorización. Este documento amplía la solicitud de OneDrive/Google Drive con el requisito de que los dispositivos concurrentes no corrompan ni bloqueen los archivos de base de datos de los demás.
Decisión
Mantener SQLite como almacén transaccional local al dispositivo. Añadir un protocolo de sincronización independiente del proveedor que intercambie objetos cifrados inmutables y autenticados mediante las API de nube. Cada dispositivo tiene su propia base de datos, identidad, contraseña y proceso de escritura local. El almacenamiento en la nube es un transporte de objetos no confiable, no una base de datos montada.
Nunca coloque los archivos activos memories.db, de diario, WAL, SHM o de bloqueo en un directorio de sincronización gestionado por la nube. No abra la base de datos mediante un sistema de archivos remoto. Mantenga la ubicación predeterminada de datos de aplicación del sistema operativo. Detectar/rechazar las raíces conocidas de OneDrive/Drive es una salvaguarda de configuración futura, que actualmente no se aplica; no se pueden detectar de forma fiable los clientes de sincronización y puntos de montaje arbitrarios.
flowchart LR
A[Device A local SQLite] --> QA[Durable outgoing queue]
QA --> C[OneDrive or Google Drive encrypted objects]
C --> VB[Download and verify]
VB --> B[Device B local SQLite]
B --> QB[Durable outgoing queue]
QB --> C
C --> VA[Download and verify]
VA --> A
Comportamiento existente y límites
crates/core/src/store.rs: un bloqueo exclusivo del sistema operativo rechaza un segundo propietario del almacén local. SQLite utilizajournal_mode=DELETE,synchronous=FULLy actualizaciones transaccionales del estado cifrado junto con puntos de control firmados. Esto protege a los procesos locales que cooperan; no es un bloqueo distribuido entre réplicas de archivos independientes.- Cada guardado serializa y cifra todo el
State; al desbloquear se comprueba todo el historial de puntos de control. Las bibliotecas más grandes aumentarán el coste de escritura y el tiempo de inicio. Es una cuestión de escalabilidad, no una prueba de que deba sustituirse SQLite. No se ha realizado una medición de carga de trabajo para esta evaluación. - La inicialización de la base de datos mantiene el bloqueo del sistema operativo del destino mientras construye una base de datos provisional privada en el mismo directorio. Confirma el estado cifrado inicial y el punto de control, cierra SQLite, sincroniza el archivo y lo publica sin sustituir un destino existente. Un fallo antes de la publicación deja disponible la ruta final para reintentarlo. Tras un fallo pueden quedar archivos
.memories-init-*abandonados; nunca se cargan como base de datos activa ni se eliminan automáticamente. Serviceprocesa las mutaciones sobre un estado clonado y las confirma antes de aceptarlas.Vault::mergevalida un candidato antes de sustituir la bóveda actual. Los historiales de revisiones firmados ya conservan las ediciones concurrentes y los marcadores de eliminación.- El IPC de escritorio acepta hasta 16 solicitudes concurrentes; el despacho del estado mantiene el mutex del servicio. En móvil se utilizan bloqueos individuales del servicio y una consulta breve del registro. Una solicitud HTTP de nube realizada directamente dentro de estas secciones críticas retrasaría otras operaciones, incluido el bloqueo. Esto es un bloqueo de ejecución, no un interbloqueo circular demostrado. Los trabajadores de nube deben liberar los bloqueos del servicio antes de la E/S de red; la gestión de solicitudes de escritorio utiliza ahora conexiones concurrentes limitadas.
- Las exportaciones de sincronización existentes son sobres de bóveda completa cifrados para el destinatario. Un dispositivo recién autenticado en la nube sigue necesitando la aprobación del dispositivo antes de poder descifrar o importar una bóveda. Un inicio de sesión en la nube no debe sustituir el emparejamiento criptográfico de dispositivos.
Alternativas de base de datos
| Opción | Adecuación a este repositorio | Decisión |
|---|---|---|
| SQLite local y objetos de nube inmutables | Reutiliza las transacciones, el cifrado, la auditoría y la gestión de conflictos; no requiere servidor | Recomendada |
| redb o LMDB como almacén local | El almacenamiento transaccional integrado puede funcionar, pero cambiar el motor no aporta combinación entre dispositivos ni bloqueo en la nube | Todavía no se justifica una migración |
| Una base de datos compartida o un archivo cifrado en una carpeta sincronizada | Las cargas independientes de archivos pueden sobrescribir versiones; los bloqueos auxiliares y diarios no están coordinados | Rechazar para la sincronización activa |
| PostgreSQL y una API de sincronización dedicada | Centraliza la concurrencia en el servidor, pero requiere operar un servicio y un protocolo separado de funcionamiento sin conexión/cifrado | Considerar solo si los requisitos del producto incluyen un servidor |
| Bibliotecas CRDT como Automerge o Yjs | Potencialmente útiles para la futura edición colaborativa de texto; no sustituyen al almacenamiento duradero ni a las comprobaciones de permisos firmadas | No introducir para credenciales ni cambios de políticas |
Cambiar SQLite a WAL no es una solución de sincronización en la nube. WAL podría ayudar más adelante a la concurrencia de lectura local después de realizar mediciones, pero requiere coordinación en el mismo host y no elimina la necesidad de un único proceso de escritura local.
Contrato de objetos de nube
Comenzar con el formato existente de sobre de bóveda completa; evitar cambiar simultáneamente el almacenamiento, la criptografía y la granularidad de sincronización. Añadir revisiones/fragmentos cifrados incrementales solo después de probar la ruta de sobres inmutables.
- Generar cada sobre una vez y guardar sus bytes exactos en la cola de salida local antes de cargarlo. Utilizar el SHA-256 del objeto completo de texto cifrado como ID lógico. Los reintentos reutilizan esos bytes; volver a cifrar en cada reintento crea un objeto nuevo.
- Los objetos y los descriptores de publicación son inmutables. No existe un
latest.jsoncompartido escribible, un archivo de bloqueo compartido, una secuencia global de nube ni una sobrescritura de base de datos. Cualquier cabecera/caché mutable futura será orientativa y recuperable mediante el listado de objetos. - Publicar primero los objetos de datos y después un descriptor firmado inmutable que vincule la versión de formato, la referencia opaca de bóveda, el remitente, el destinatario, los hashes y longitudes de los objetos y el predecesor/secuencia por dispositivo. Utilizar identificadores de texto cifrado, no títulos en texto claro, nombres de cuenta ni hashes de contraseñas, en los nombres de archivo.
- Un descriptor puede hacerse visible antes que sus dependencias, porque el listado y la entrega de nube pueden retrasarse o cambiar de orden. Mantenerlo pendiente hasta que todos los objetos estén presentes y sean válidos. Nunca importarlo parcialmente ni avanzar su estado de aplicación mientras falten dependencias.
- Verificar la longitud, el hash del texto cifrado, la firma, el destinatario, el ámbito de bóveda, la autorización actual y las invariantes existentes del historial antes de confirmar. Los ETag y hashes del proveedor son metadatos de transporte, no autoridad criptográfica.
- Los objetos reproducidos y los archivos duplicados del proveedor son inocuos: deduplicar por ID lógico de objeto verificado, no por nombre de archivo. La ausencia de archivos de nube nunca implica una eliminación a nivel de aplicación; la eliminación es un marcador firmado.
Un proveedor de nube puede eliminar o retener objetos. Las cabeceras/puntos de control conocidos localmente pueden detectar algunas reversiones, pero un dispositivo nuevo sin un punto de control más reciente y confiable no puede demostrar que tiene el estado más nuevo. Este diseño proporciona integridad y conflictos explícitos, no disponibilidad ni actualidad garantizadas frente a un proveedor malicioso.
Transacciones y ciclo de vida del trabajador
El proceso de escritura local controla el estado de aplicación, las actualizaciones de puntos de control, el estado de las colas de salida/entrada y las confirmaciones de sincronización. Utilizar dos secciones críticas breves alrededor de la actividad de red:
- Preparar localmente: autorizar, capturar una exportación coherente y confirmar el trabajo de carga cifrado exacto y su vinculación de generación/sesión. Una edición del usuario y su marcador de cambios/exportación deben confirmarse atómicamente para que un fallo no pueda perder trabajo.
- Transferir sin bloqueo: un trabajador separado realiza la renovación OAuth, la carga, la descarga, la paginación y los reintentos sin mantener el mutex del servicio ni una transacción de escritura de base de datos. Limitar la concurrencia, los tamaños de respuesta, los reintentos y los plazos de conexión/lectura. Respetar las limitaciones del proveedor y
Retry-After. - Confirmar localmente: recuperar el control de escritura, verificar la autorización y la generación de sesión, combinar con el estado actual en lugar de una copia antigua capturada y confirmar conjuntamente el estado, el punto de control, los ID aplicados y el estado de entrada. Un cursor de listado del proveedor solo puede avanzar cuando el trabajo descubierto sea duradero.
Bloquear/cerrar sesión cancela los trabajos e invalida los resultados tardíos. Ningún nuevo descifrado/importación puede completarse en una sesión invalidada. La cancelación no puede retirar una carga de texto cifrado ya aceptada por el proveedor. Mantener reanudables los trabajos incompletos; una carga correcta significa persistencia en la nube, no confirmación por otro dispositivo.
Los tokens OAuth permanecen cifrados y locales al dispositivo, excluidos de los objetos de nube, los paquetes de aprobación de dispositivos, las copias de seguridad exportadas, la búsqueda y MCP. La rotación de tokens de renovación debe guardarse antes de que las operaciones posteriores utilicen el token nuevo; la renovación concurrente se serializa por cuenta, sin mantener el bloqueo del servicio durante el intercambio HTTP. Nunca añadir una operación pública de lectura de tokens para los trabajadores.
Comportamiento específico de cada proveedor
| Proveedor | Almacenamiento de aplicación | Requisitos de carga/reintento |
|---|---|---|
| OneDrive | Carpeta de aplicación de Microsoft Graph, Files.ReadWrite.AppFolder |
Utilizar nombres de objeto inmutables y el comportamiento de conflicto de sesión de carga fail; si existe un objeto, descargarlo/verificarlo en lugar de sustituirlo. Utilizar solicitudes condicionales solo cuando ese endpoint documente su compatibilidad. |
| Google Drive | appDataFolder, ámbito drive.appdata |
Generar previamente y guardar los ID de archivo con los trabajos para reintentar de forma segura las creaciones ambiguas; no considerar únicos los nombres de archivo. Verificar los ID/contenido existentes después de un conflicto. |
Ambos adaptadores deben implementar paginación, transferencias reanudables, descargas limitadas, recuperación de respuestas perdidas y gestión de errores específica del proveedor. No suponer que Drive admite renombrado/bloqueo del sistema de archivos ni la misma semántica de precondiciones que Graph. Las URL de descarga redirigidas y las URL de carga reanudable requieren reglas explícitas de reenvío de credenciales para que los tokens portadores OAuth no se envíen a hosts arbitrarios.
El diseño inicial de carpetas de aplicación sirve a los dispositivos emparejados de un usuario conectados a la misma cuenta y aplicación del proveedor. El almacenamiento de datos de aplicación de Google no admite compartir archivos entre cuentas diferentes. Compartir bóvedas entre usuarios requiere un diseño explícito de transporte/compartición; no debe ampliar silenciosamente los ámbitos OAuth. OneDrive y Google Drive son réplicas independientes si ambos están habilitados, no carpetas sincronizadas entre sí. Registrar por separado las transferencias pendientes y las completadas.
Se requieren registros de clientes OAuth y configuración de consentimiento del proveedor antes de verificar cuentas reales. La autorización de la CLI debe utilizar OAuth en el navegador con PKCE, validación de estado y una devolución de llamada de bucle local. La aplicación móvil requiere su flujo de autorización nativo admitido y la gestión de su ciclo de vida; no se deben copiar sin cambios a móvil las suposiciones de devolución de llamada de escritorio.
Conflictos, recuperación y retención
- Conservar ambas revisiones concurrentes de credenciales/tarjetas. Exigir una selección o resolución explícita antes de usarlas; no elegir la marca de tiempo de reloj más reciente ni combinar cadenas de contraseña. Reutilizar las reglas existentes de bifurcación de políticas y cuarentena de épocas anteriores.
- Un fallo de suma de comprobación/firma rechaza el objeto entrante y conserva el estado local anterior. Distinguir en el estado las entradas corruptas, las dependencias ausentes, el inicio de sesión caducado, la cuota y el conflicto de contenido, en lugar de informar de todos como errores de bloqueo. Una respuesta cuyos bytes no coinciden con su hash sigue admitiendo reintentos con espera progresiva. Los objetos cuyo hash se ha verificado pero que son inválidos tienen comprobantes de rechazo separados; nunca cuentan como dependencias aplicadas correctamente. Los rechazos impiden informar de una sincronización correcta y siguen visibles por proveedor en el estado de nube. Desconectar un proveedor borra sus comprobantes de rechazo para que una conexión posterior pueda volver a explorar. Los comprobantes antiguos que mezclaban rechazos con importaciones correctas se borran una vez; los comprobantes existentes de paquetes firmados siguen evitando las importaciones duplicadas.
- El descubrimiento de directorios tiene un presupuesto de entrada de 4096 entradas más 16 espacios reservados para dependencias. Los descriptores verificados programan los objetos ausentes directamente por hash, antes de las descargas ordinarias. Los búferes de sobres sin referencias pueden descartarse bajo presión y descargarse de nuevo cuando llegue su descriptor. Una página parcial de directorio nunca avanza el cursor más allá de los ID que no se admitieron.
- Cada confirmación de estado elimina las exportaciones en cola dirigidas a destinatarios cuyo acceso actual a la bóveda se haya revocado. Los trabajadores validan su sesión entre operaciones de objetos, incluso después de la asignación de ID del proveedor. Los ID de archivo de Google se guardan en la cola de salida cifrada antes de la primera carga, de modo que las confirmaciones perdidas y los reinicios reutilizan el mismo objeto inmutable del proveedor.
- Conservar inicialmente los objetos de nube antiguos. Una recolección de basura segura requiere confirmaciones por dispositivo, una política para dispositivos sin conexión, gestión de revocaciones y una política de retención para recuperación. La ausencia en listados y el tiempo transcurrido por sí solos no bastan.
- Mantener copias de seguridad cifradas independientes. La sincronización entre dispositivos también distribuye eliminaciones válidas y no sustituye la recuperación a un momento determinado.
- Añadir una ruta de migración que contemple la recuperación antes de una migración de esquema: bloquear localmente, conservar una copia de seguridad verificada, migrar transaccionalmente, verificar y después activar. Una migración fallida debe dejar utilizable el almacén anterior.
Secuencia de implementación
- Completado. Solicitudes limitadas al proceso de escritura (
CloudSyncEnqueue/CloudSyncWork/CloudSyncAck) y comprobantes duraderos de salida/entrada dentro delStatecifrado. El cifrado de SQLite,journal_mode=DELETEy los guardados de todo el estado no cambian. No hay migración a WAL ni de motor de base de datos. - Completado, solo transporte local. Ejecutor de objetos inmutables independiente del proveedor (
sync::execute) con inyección de fallos deMemoryTransport. Las pruebas cubren el reintento de bytes exactos, la reapertura tras la confirmación, la verificación de escrituras duplicadas, el descriptor antes que los datos, los objetos corruptos, la invalidación por bloqueo/cierre de sesión y dos dispositivos emparejados. Este transporte de prueba no utiliza cuentas reales de nube. - Completado, solo fixtures locales. Renovación de tokens, adaptadores de objetos inmutables de OneDrive/Google Drive,
CloudSyncRundel daemon (HTTP fuera del bloqueo de escritura) ymemories cloud sync. No se autorizaron cuentas reales de Microsoft/Google. - Implementado, consentimiento nativo real pendiente. El inicio de sesión móvil utiliza AppAuth; la sincronización en primer plano comparte el ejecutor del núcleo. Pasar a segundo plano bloquea todos los identificadores y cancela los resultados tardíos. Las pruebas de regresión de widgets y FFI nativa cubren la cancelación; el consentimiento con proveedores/dispositivos reales sigue siendo un requisito de publicación. La programación en segundo plano no está habilitada intencionadamente.
- Referencia disponible. Consulte rendimiento para la medición reproducible de una compilación de publicación en un entorno desechable del tamaño de biblioteca, la latencia de guardado/desbloqueo/búsqueda y el tráfico de sincronización. Si es necesario, migrar el bloque local de estado completo a filas cifradas por bóveda/revisión/clave con comprobantes transaccionales y después introducir objetos de nube incrementales. Conservar los ID firmados, el historial de revisiones, la privacidad del texto cifrado y la recuperación de copias de seguridad.
Evidencia de validación y requisitos pendientes
En Windows, crates/core/tests/storage_resilience.rs ejercita almacenes desechables y subprocesos. Tres escenarios pasaron durante esta evaluación:
- Un proceso separado se rechaza mientras otro posee el almacén; terminar el propietario libera el bloqueo del sistema operativo y el estado cifrado original puede abrirse de nuevo.
- Un proceso hijo inicia una transacción de escritura SQLite, fuerza las páginas modificadas a un diario de reversión y se termina antes de confirmar.
Store::unlockvalida el estado/punto de control original yPRAGMA integrity_checkdevuelveok. - Dos dispositivos autorizados editan el mismo recurso de forma independiente. Ambos órdenes de combinación convergen, la reproducción es idempotente, se conservan ambas cabeceras y se rechaza una instantánea entrante con un padre ausente sin modificar el estado correcto.
El auxiliar ignorado deliberadamente se lanza de forma explícita desde las pruebas principales. Ejecute:
cargo test -p memories-core --test storage_resilience --locked -j 1 -- --test-threads=1
cargo test -p memories-core --lib store::tests --locked -j 1 -- --test-threads=1
Las pruebas de regresión de inicialización en store.rs terminan un subproceso después de la preparación provisional pero antes de la publicación y luego recrean/desbloquean el almacén final. También verifican que el bloqueo del destino siga retenido y que nunca se sobrescriba un destino existente o recién aparecido. Las pruebas OAuth cubren la invalidación de resultados tardíos después del bloqueo.
Estas son pruebas de fallos de procesos locales y de modelo, no una demostración frente a pérdidas reales de alimentación, fallos de disco/controlador, sustitución de archivos por clientes de nube o comportamiento real de proveedores. Los fallos por disco lleno, las bibliotecas grandes, la pérdida de alimentación al publicar y los límites de reintento/publicación de nube aún necesitan pruebas. No se autorizó ni se escribió en ninguna cuenta real de nube durante esta evaluación.
Referencias
Documentación oficial consultada el 2026-09-09:
- SQLite a través de una red
- Restricciones de WAL en SQLite
- Causas de corrupción de SQLite
- Carpetas de aplicación de OneDrive
- Sesiones de carga de Graph y comportamiento ante conflictos
- Datos de aplicación de Google Drive
- Cargas de Google Drive e ID generados previamente
- OAuth de Google para aplicaciones nativas
- Capacidades de redb