knowledgeC.db-wal: recuperar eventos recientes del WAL
Por qué los últimos eventos de knowledgeC suelen estar solo en el WAL, cómo funcionan frames, salts y checkpoints, y cómo leer las filas añadidas o eliminadas.
Resumen. knowledgeC.db funciona en modo WAL de SQLite: las transacciones nuevas se añaden al final de knowledgeC.db-wal y solo se copian al archivo principal en un checkpoint. Hasta entonces, los eventos más recientes existen solo en el WAL, y las filas eliminadas por una transacción reciente siguen en el archivo principal. Recolecte el -wal junto con la base de datos, trabaje sobre copias porque abrir el original puede provocar un checkpoint, y lea el WAL como una lista de versiones de página confirmadas, validadas mediante salts y sumas de comprobación. KnowledgeC Parser aplica los frames confirmados y marca cada fila como "only in the WAL" (solo en el WAL) o "removed by the WAL" (eliminada por el WAL).
Por qué el WAL importa en knowledgeC
Muchos análisis de la base de datos knowledgeC consultan solo knowledgeC.db. En un Mac en ejecución o apagado recientemente, eso a menudo pasa por alto la parte de la cronología que más le interesa: las últimas horas antes de la recolección. CoreDuet escribe las filas cuando terminan los intervalos, las filas van al WAL y solo llegan al archivo principal cuando SQLite hace un checkpoint. Un analista que examina solo el archivo principal ve una cronología que se detiene antes de tiempo, y puede concluir que el Mac estaba inactivo cuando no lo estaba.
También ocurre lo contrario. La depuración por retención elimina filas antiguas; la eliminación es una transacción del WAL, y el archivo principal conserva las filas antiguas hasta el siguiente checkpoint. Ambos casos solo son visibles si tiene los dos archivos y los compara.
Cómo funciona el WAL de SQLite
La referencia es la documentación de SQLite sobre write-ahead logging y sobre el formato del archivo WAL. En resumen: la base de datos es un conjunto de páginas de tamaño fijo. En modo WAL, un proceso escritor nunca modifica directamente las páginas del archivo principal. Añade al final del archivo -wal nuevas versiones de las páginas modificadas; los lectores buscan primero la última versión confirmada de una página en el WAL y, si no la encuentran, recurren al archivo principal.
La cabecera del WAL
El -wal empieza con una cabecera de 32 bytes, en big-endian:
| Desplazamiento | Tamaño | Campo |
|---|---|---|
| 0 | 4 | Magic: 0x377f0682 o 0x377f0683 (determina el orden de bytes de la suma de comprobación) |
| 4 | 4 | Versión del formato de archivo (3007000) |
| 8 | 4 | Tamaño de página de la base de datos |
| 12 | 4 | Número de secuencia del checkpoint |
| 16 | 4 | Salt-1 |
| 20 | 4 | Salt-2 |
| 24 | 8 | Suma de comprobación de los primeros 24 bytes |
Frames
Tras la cabecera vienen los frames: una cabecera de frame de 24 bytes seguida de una página completa.
| Desplazamiento | Tamaño | Campo |
|---|---|---|
| 0 | 4 | Número de página |
| 4 | 4 | Tamaño de commit: en el último frame de una transacción, el tamaño de la base de datos en páginas tras el commit; 0 en los demás |
| 8 | 4 | Salt-1, copiado de la cabecera del WAL |
| 12 | 4 | Salt-2, copiado de la cabecera del WAL |
| 16 | 8 | Suma de comprobación acumulada sobre la cabecera y todos los frames hasta este |
Qué frames cuentan
Un frame solo es válido si sus salts coinciden con la cabecera del WAL y su suma de comprobación coincide con la suma acumulada calculada desde el inicio del archivo. La lectura se detiene en el primer frame que no supera alguna de las dos pruebas. Entre los frames válidos, solo los que llegan hasta el último frame de commit (tamaño de commit distinto de cero) pertenecen a transacciones confirmadas. Los frames posteriores son una transacción inacabada que SQLite ignora, así que no deben tratarse como contenido de la base de datos. Si una página aparece en varios frames confirmados, prevalece el más reciente.
Checkpoints y reinicios
Un checkpoint copia de vuelta al archivo principal la última versión confirmada de cada página del WAL. Por defecto, SQLite ejecuta uno automáticamente cuando el WAL alcanza unas 1000 páginas y, normalmente, cuando se cierra la última conexión a la base de datos. Tras un checkpoint, el siguiente proceso escritor puede reiniciar el WAL desde el principio: salt-1 se incrementa y salt-2 cambia, y el archivo no se trunca necesariamente. Por tanto, pueden quedar frames de la generación anterior después de los válidos. Sus salts ya no coinciden, así que SQLite los ignora; son versiones de página obsoletas, no forman parte de la base de datos actual.
El archivo -shm
knowledgeC.db-shm es el índice del WAL: memoria compartida que ayuda a las conexiones a encontrar rápidamente las páginas en el WAL. No contiene datos que no estén en el WAL y puede reconstruirse a partir de él. Recoléctelo con los otros dos archivos para que la colección esté completa, pero el contenido confirmado procede del -wal.
Dos casos que cambian una cronología
Filas que solo están en el WAL
Una fila insertada desde el último checkpoint existe solo como versión de página en el -wal. Si analiza solo el archivo principal, la fila no aparece. Suelen ser las filas más recientes, así que el hueco queda al final de la cronología, justo antes de la recolección.
Filas eliminadas por el WAL
Un DELETE confirmado produce, en el WAL, nuevas versiones de página sin la fila. El archivo principal sigue conteniendo la página antigua, con la fila. Leer la base de datos «como lo haría SQLite» oculta la fila; leer solo el archivo principal la muestra. Comparar ambas vistas le indica que la fila existió y que se eliminó después del último checkpoint.
En knowledgeC, la causa habitual es la depuración de eventos antiguos por retención. Una fila eliminada por el WAL no es, por sí sola, evidencia de un borrado deliberado; compare su fecha con la ventana de retención que midió en la evidencia antes de sacar ninguna conclusión. Las filas modificadas en su sitio conservan su identidad y simplemente muestran sus valores más recientes.
Trabajar de forma segura
- No abra nunca el original. Abrir una base de datos WAL con un cliente SQLite normal puede provocar un checkpoint: las páginas del WAL se escriben en el archivo principal y el WAL puede reiniciarse. Los dos casos anteriores desaparecen.
- Conserve una copia intacta. Calcule el hash de los tres archivos tal como se recolectaron y analice una segunda copia.
- Sepa qué vista está mirando. Un cliente SQLite que recibe ambos archivos muestra la base de datos con el WAL aplicado, y puede provocar un checkpoint en su copia. Una copia del archivo principal sin su
-walmuestra el estado en el último checkpoint. Necesita ambas vistas para ver qué añadió y qué eliminó el WAL. - Deje constancia de lo que no tenía. Si no se recolectó el
-wal, indíquelo en el informe: la ausencia de eventos recientes es entonces una laguna de la recolección, no un hallazgo. Cómo recolectarlo se explica en knowledgeC.db: ubicación y adquisición.
Cómo trata el WAL KnowledgeC Parser
KnowledgeC Parser lee los archivos en solo lectura en su navegador y empareja cada knowledgeC.db con el -wal del mismo nombre. Después:
- comprueba la cabecera del WAL: magic, tamaño de página igual al de la base de datos, suma de comprobación de la cabecera;
- lee los frames en orden mientras los salts coinciden y la suma de comprobación acumulada es correcta, y se detiene en el primer fallo con una nota como "checksum mismatch at frame N" o "salt mismatch";
- aplica solo las transacciones confirmadas, indica cuántos frames de una transacción inacabada se ignoraron y superpone al archivo principal la última versión confirmada de cada página;
- lee
ZOBJECTdos veces, con y sin el WAL, y empareja las filas por número de registro y UUID.
Cada evento de knowledgeC lleva entonces, cuando procede, una de dos marcas: only in the WAL (presente con el WAL, ausente del archivo principal) o removed by the WAL (presente en el archivo principal, eliminado por una transacción confirmada del WAL). El informe del archivo resume el resultado como "WAL applied: N committed frame(s), X row(s) only in the WAL, Y row(s) removed by it". Cuando llega una base de datos en modo WAL sin su -wal, el informe lo indica y advierte de que pueden faltar eventos recientes. No se usan los frames obsoletos de generaciones anteriores del WAL.
Un ejemplo sintético: FIN-MBP-03
La colección de ejemplo incluida con la herramienta es sintética; el Mac, la usuaria y los eventos son ficticios. En el MacBook FIN-MBP-03, el archivo principal del knowledgeC.db del sistema contiene filas escritas hasta aproximadamente las 10:00 UTC del 2026-09-14. Todo lo escrito después está en una transacción confirmada adicional en el WAL.
Analizada sin el -wal, la base de datos muestra que el último intervalo de desbloqueo de la mañana de dana.whitlock termina a las 09:58 UTC, cuando bloqueó el Mac para ir a comer, y nada después. Analizada con él, el informe indica "WAL applied" con unas pocas decenas de filas que solo están en el WAL y una eliminada por él. Entre las filas que solo están en el WAL figuran:
- un intervalo de desbloqueo en
/device/isLockedque empieza a las 10:04:31 UTC (ZSECONDSFROMGMT7200, es decir, las 12:04 hora local) y termina a las 10:49 UTC; - intervalos
/app/usagede Safari, Finder, Utilidad de Archivo, Terminal y Ajustes del Sistema dentro de esa ventana.
La fila eliminada por el WAL es un intervalo /app/usage de com.apple.mail del 2026-08-10: un evento antiguo depurado por retención que sigue en el archivo principal. Ilustra bien por qué «eliminado» no debe interpretarse como «borrado intencionadamente».
Lo que el WAL aporta aquí es la existencia y la cronología de la actividad durante la pausa para comer. No dice quién estaba ante el teclado, ni nombra archivos o comandos. Para esa interpretación, y para la corroboración con Biome y otras fuentes, consulte quién estaba ante el teclado. Para reproducir el ejemplo, cargue la muestra desde la página de inicio o siga analizar knowledgeC y Biome en el navegador.
Lo que el WAL no le dice
- Cuándo se escribió un frame. Los frames no llevan marcas de tiempo. Una fila del WAL se confirmó después del último checkpoint; la hora de su evento procede de
ZSTARTDATEyZENDDATE, en tiempo absoluto de Mac, no de su posición en el archivo. - Nada anterior al último reinicio. SQLite y la herramienta ignoran los frames obsoletos de una generación anterior; quedan fuera de la base de datos actual.
- Qué se eliminó antes del último checkpoint. Una vez que una eliminación pasa por un checkpoint, la fila desaparece de ambas vistas; los restos en el espacio libre son un problema de carving, fuera del alcance de este flujo de trabajo.