# Audit Log System - Diseño de Arquitectura

## Visión General

Sistema de log histórico externo para zcatsql que permite:
1. **v1.0**: Auditoría completa de operaciones con integridad verificable
2. **v2.0**: Navegación temporal (time travel) a cualquier punto en el historial

## Arquitectura

```
┌─────────────────────────────────────────────────────────────────────┐
│                          Aplicación                                  │
│  ┌─────────────┐                                                    │
│  │   AuditCtx  │ ◀── user_id, app_name (proporcionado por app)     │
│  └─────────────┘                                                    │
└────────┬────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────────┐
│                         AuditLog                                     │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │  Hooks: pre-update, update, commit, rollback                 │   │
│  │  Captura: SQL + valores antes/después + metadatos            │   │
│  └──────────────────────────────────────────────────────────────┘   │
│                              │                                       │
│                              ▼                                       │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │  Entry Buffer (en memoria hasta commit)                      │   │
│  │  - Agrupa operaciones por transacción                        │   │
│  │  - Descarta si rollback                                      │   │
│  └──────────────────────────────────────────────────────────────┘   │
│                              │                                       │
│                              ▼                                       │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │  LogWriter                                                    │   │
│  │  - Escribe a archivo                                         │   │
│  │  - Gestiona rotación                                         │   │
│  │  - Calcula hash chain                                        │   │
│  └──────────────────────────────────────────────────────────────┘   │
└────────┬────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────────┐
│                      Sistema de Archivos                             │
│                                                                      │
│  mydb_audit_0001.log  ──▶  mydb_audit_0002.log  ──▶  ...            │
│  (hash chain)              (hash chain)                              │
│                                                                      │
│  mydb_audit_index.json  ◀── Índice de archivos y rangos de tiempo   │
└─────────────────────────────────────────────────────────────────────┘
```

---

## Formato de Entrada de Log

Cada operación se registra en formato JSON Lines (una línea por entrada):

```json
{
  "seq": 1,
  "ts": "2025-12-08T14:32:15.123456Z",
  "tx_id": 42,
  "ctx": {
    "user": "alice",
    "app": "simifactu",
    "host": "workstation-01",
    "pid": 12345
  },
  "op": "UPDATE",
  "table": "invoices",
  "rowid": 157,
  "sql": "UPDATE invoices SET status = 'paid' WHERE id = 157",
  "before": {
    "id": 157,
    "status": "pending",
    "amount": 1500.00
  },
  "after": {
    "id": 157,
    "status": "paid",
    "amount": 1500.00
  },
  "prev_hash": "a1b2c3d4e5f6...",
  "hash": "f6e5d4c3b2a1..."
}
```

### Campos

| Campo | Tipo | Descripción |
|-------|------|-------------|
| `seq` | u64 | Número de secuencia global (nunca se repite) |
| `ts` | ISO8601 | Timestamp con microsegundos |
| `tx_id` | u64 | ID de transacción (agrupa operaciones) |
| `ctx` | object | Contexto: user, app, host, pid |
| `op` | string | INSERT, UPDATE, DELETE |
| `table` | string | Nombre de la tabla |
| `rowid` | i64 | SQLite rowid |
| `sql` | string | SQL ejecutado (si está disponible) |
| `before` | object? | Valores antes (NULL para INSERT) |
| `after` | object? | Valores después (NULL para DELETE) |
| `prev_hash` | string | Hash de la entrada anterior |
| `hash` | string | SHA-256 de esta entrada (sin el campo hash) |

---

## Estructura de Archivos

```
mydb_audit/
├── index.json              # Índice maestro
├── 0001_20251208_143200.log   # Archivo de log
├── 0002_20251209_000000.log   # Siguiente archivo
└── ...
```

### index.json

```json
{
  "version": 1,
  "db_name": "mydb.sqlite",
  "created": "2025-12-08T14:32:00Z",
  "files": [
    {
      "id": 1,
      "filename": "0001_20251208_143200.log",
      "seq_start": 1,
      "seq_end": 15420,
      "ts_start": "2025-12-08T14:32:00Z",
      "ts_end": "2025-12-08T23:59:59Z",
      "entries": 15420,
      "bytes": 8523410,
      "first_hash": "000000...",
      "last_hash": "a1b2c3...",
      "closed": true
    },
    {
      "id": 2,
      "filename": "0002_20251209_000000.log",
      "seq_start": 15421,
      "seq_end": null,
      "ts_start": "2025-12-09T00:00:00Z",
      "ts_end": null,
      "entries": 342,
      "bytes": 182340,
      "first_hash": "a1b2c3...",
      "last_hash": "d4e5f6...",
      "closed": false
    }
  ],
  "rotation": {
    "max_bytes": 104857600,
    "max_age_days": 30
  }
}
```

---

## Rotación de Archivos

Criterios (configurable, se aplica el primero que se cumpla):

1. **Por tamaño**: cuando alcanza `max_bytes` (default: 100MB)
2. **Por tiempo**: cuando pasa `max_age_days` (default: 30 días)
3. **Manual**: API para forzar rotación

Al rotar:
1. Cerrar archivo actual (marcar `closed: true`)
2. Crear nuevo archivo
3. Primer hash del nuevo = último hash del anterior (continuidad)
4. Actualizar index.json

---

## Hash Chain (Integridad)

```
Entry 1:  prev_hash = "0000...0000" (genesis)
          hash = SHA256(entry_1_without_hash)

Entry 2:  prev_hash = hash(Entry 1)
          hash = SHA256(entry_2_without_hash)

Entry N:  prev_hash = hash(Entry N-1)
          hash = SHA256(entry_N_without_hash)
```

### Verificación

```zig
pub fn verifyChain(log_dir: []const u8) !VerifyResult {
    // 1. Leer index.json
    // 2. Para cada archivo:
    //    - Verificar que first_hash == last_hash del anterior
    //    - Para cada entrada:
    //      - Recalcular hash
    //      - Verificar prev_hash == hash anterior
    // 3. Reportar: OK o primera entrada corrupta
}
```

---

## API v1.0 - Auditoría

```zig
const AuditLog = @import("zcatsql").audit.AuditLog;

// Configuración
const config = AuditLog.Config{
    .log_dir = "./mydb_audit",
    .rotation = .{
        .max_bytes = 100 * 1024 * 1024,  // 100MB
        .max_age_days = 30,
    },
    .context = .{
        .app_name = "simifactu",
        .user_id = null,  // Se puede cambiar dinámicamente
    },
    .capture = .{
        .sql = true,
        .before_values = true,
        .after_values = true,
    },
};

// Inicializar
var audit = try AuditLog.init(allocator, &db, config);
defer audit.deinit();

// Cambiar usuario dinámicamente (ej: después de login)
audit.setUser("alice");

// El audit log captura automáticamente via hooks
try db.exec("INSERT INTO users (name) VALUES ('Bob')");

// Forzar rotación manual
try audit.rotate();

// Verificar integridad
const result = try audit.verify();
if (!result.valid) {
    std.debug.print("Corrupto en seq {}\n", .{result.first_invalid_seq});
}

// Estadísticas
const stats = audit.stats();
std.debug.print("Entries: {}, Files: {}\n", .{stats.total_entries, stats.file_count});
```

---

## API v2.0 - Time Travel

```zig
const TimeMachine = @import("zcatsql").audit.TimeMachine;

// Inicializar con log existente
var tm = try TimeMachine.init(allocator, "./mydb_audit");
defer tm.deinit();

// Obtener estado en un momento específico
var snapshot_db = try tm.getStateAt("2025-12-08T15:30:00Z");
defer snapshot_db.close();
// snapshot_db es una DB en memoria con el estado de ese momento

// O navegar por secuencia
var snapshot_db2 = try tm.getStateAtSeq(1000);

// Replay: reconstruir DB desde cero
var rebuilt_db = try tm.replay(":memory:");
defer rebuilt_db.close();

// Replay parcial: desde snapshot hasta punto
var partial_db = try tm.replayRange(
    "base_snapshot.sqlite",  // Punto de partida
    5000,                     // seq_start
    8000,                     // seq_end
);

// Undo: revertir últimas N operaciones
try tm.undoLast(&db, 5);

// Redo: re-aplicar operaciones revertidas
try tm.redo(&db, 3);

// Ver historial de una fila específica
const history = try tm.getRowHistory("invoices", 157);
defer allocator.free(history);
for (history) |entry| {
    std.debug.print("{s}: {s} -> {s}\n", .{entry.ts, entry.op, entry.after});
}
```

---

## Consideraciones de Rendimiento

### v1.0
- **Overhead mínimo**: hooks son muy ligeros
- **Buffering**: acumular en memoria hasta commit
- **Async write**: opción de escribir en thread separado
- **Compresión**: opcional, gzip por archivo cerrado

### v2.0
- **Snapshots periódicos**: cada N operaciones o tiempo
  - Reduce tiempo de replay
  - `snapshot_0001.sqlite` + logs desde ahí
- **Índices en memoria**: para búsqueda rápida por tiempo/tabla/rowid
- **Lazy loading**: cargar solo los logs necesarios

---

## Flujo de Datos

### Captura (v1.0)

```
1. App ejecuta SQL
        │
        ▼
2. Pre-update hook captura valores ANTES
        │
        ▼
3. SQLite ejecuta la operación
        │
        ▼
4. Update hook captura operación + rowid
        │
        ▼
5. Guardar en buffer de transacción
        │
        ├── Commit hook ──▶ Escribir buffer a archivo
        │
        └── Rollback hook ──▶ Descartar buffer
```

### Replay (v2.0)

```
1. Encontrar snapshot más cercano anterior al tiempo deseado
        │
        ▼
2. Cargar snapshot a memoria
        │
        ▼
3. Leer logs desde snapshot hasta tiempo deseado
        │
        ▼
4. Para cada entrada:
   - INSERT: ejecutar INSERT con valores `after`
   - UPDATE: ejecutar UPDATE con valores `after`
   - DELETE: ejecutar DELETE
        │
        ▼
5. Retornar DB en estado deseado
```

### Undo

```
1. Leer entrada a revertir
        │
        ▼
2. Según operación:
   - INSERT: DELETE WHERE rowid = X
   - UPDATE: UPDATE SET (valores `before`) WHERE rowid = X
   - DELETE: INSERT con valores `before`
        │
        ▼
3. Registrar operación de undo en el log (para auditoría)
```

---

## Seguridad (Futuro)

- **Encriptación**: AES-256-GCM por archivo
- **Firma digital**: además de hash, firmar con clave privada
- **Acceso**: permisos de lectura separados de escritura

---

## Módulos a Implementar

```
src/
├── audit/
│   ├── mod.zig           # Exports
│   ├── log.zig           # AuditLog principal
│   ├── writer.zig        # Escritura a archivo
│   ├── entry.zig         # Estructura de entrada
│   ├── context.zig       # Contexto (user, app, host)
│   ├── rotation.zig      # Gestión de rotación
│   ├── index.zig         # Índice de archivos
│   ├── hash.zig          # Hash chain
│   ├── verify.zig        # Verificación de integridad
│   └── time_machine.zig  # Time travel (v2.0)
```

---

## Fases de Implementación

### Fase 1: Core (v1.0)
1. Estructura de entrada y serialización JSON
2. Writer con rotación básica
3. Hash chain
4. Hooks para captura
5. Contexto y metadatos

### Fase 2: Integridad (v1.0)
1. Verificación de cadena
2. Index.json completo
3. Estadísticas

### Fase 3: Time Travel (v2.0)
1. Replay hacia adelante
2. Undo/Redo
3. getStateAt()
4. getRowHistory()

### Fase 4: Optimización (v2.0)
1. Snapshots automáticos
2. Índices en memoria
3. Compresión de archivos cerrados

---

## Preguntas Resueltas

| Pregunta | Decisión |
|----------|----------|
| ¿Qué capturar? | SQL + valores antes/después |
| ¿Cómo identificar contexto? | Mixto: auto-detectar host/pid, app proporciona user/app_name |
| ¿Cuándo rotar? | Configurable: por defecto 100MB o 30 días |
| ¿Granularidad time travel? | Cualquier operación (seq exacto) |
