| id |
type |
title |
status |
decision_date |
updated_at |
simco_version |
stakeholders |
tags |
| ADR-0008 |
ADR |
Audit Log Retention |
Accepted |
2026-01-10 |
2026-01-10 |
4.0.1 |
|
| audit |
| retention |
| compliance |
| storage |
|
ADR-0008: Audit Log Retention
Metadata
| Campo |
Valor |
| ID |
ADR-0008 |
| Estado |
Accepted |
| Fecha |
2026-01-10 |
| Autor |
Architecture Team |
| Supersede |
- |
Contexto
MiChangarrito registra audit logs de todas las acciones del sistema para compliance, debugging y seguridad. Necesitamos definir:
- Cuanto tiempo mantener los logs
- Como archivar logs antiguos
- Como purgar datos sin perder trazabilidad
Decision
Implementamos politica de retencion configurable por tenant con archivado opcional a storage antes de purga.
- Default: 90 dias en base de datos
- Archivado: Opcional a S3/R2 antes de purga
- Notificacion: 7 dias antes de purga
- Configuracion: Por tenant segun plan
Alternativas Consideradas
Opcion 1: Retencion infinita
- Pros:
- Nunca se pierde informacion
- Simple
- Cons:
- Crecimiento ilimitado de BD
- Costos de almacenamiento
- Performance degradada
Opcion 2: Retencion fija (30 dias)
- Pros:
- Simple de implementar
- Predecible
- Cons:
- No flexible para diferentes necesidades
- Puede no cumplir regulaciones
Opcion 3: Retencion configurable + archivado (Elegida)
- Pros:
- Flexible por tenant
- Archivado economico
- Cumple regulaciones
- Cons:
- Mas complejo
- Necesita job de limpieza
Consecuencias
Positivas
- Compliance: Tenants pueden configurar segun sus necesidades
- Performance: BD no crece indefinidamente
- Economia: Archivado en storage es barato
- Flexibilidad: Diferentes politicas por plan
Negativas
- Complejidad: Job de limpieza y archivado
- Acceso: Logs archivados requieren restauracion
Implementacion
Configuracion por Plan
| Plan |
Retencion BD |
Archivado |
Restauracion |
| Basic |
30 dias |
No |
N/A |
| Pro |
90 dias |
Si (1 ano) |
Bajo demanda |
| Enterprise |
365 dias |
Si (7 anos) |
Autoservicio |
Job de Limpieza
@Cron('0 3 * * *') // 3 AM diario
async cleanupAuditLogs() {
const tenants = await this.tenantService.findAll();
for (const tenant of tenants) {
const config = await this.getRetentionConfig(tenant.id);
const cutoffDate = subDays(new Date(), config.retentionDays);
// 1. Archivar si esta habilitado
if (config.archiveBeforeDelete) {
await this.archiveLogs(tenant.id, cutoffDate);
}
// 2. Purgar logs antiguos
await this.purgeLogs(tenant.id, cutoffDate);
this.logger.log(`Cleaned audit logs for tenant ${tenant.id}`, {
cutoffDate,
archived: config.archiveBeforeDelete,
});
}
}
Archivado
async archiveLogs(tenantId: string, olderThan: Date) {
const logs = await this.auditRepository.find({
where: {
tenantId,
createdAt: LessThan(olderThan),
},
});
if (logs.length === 0) return;
// Comprimir y subir a storage
const archive = await this.compressLogs(logs);
const key = `audit-archives/${tenantId}/${format(new Date(), 'yyyy-MM')}.json.gz`;
await this.storageService.upload(key, archive, {
contentType: 'application/gzip',
metadata: {
tenantId,
recordCount: logs.length.toString(),
dateRange: `${logs[0].createdAt}-${logs[logs.length - 1].createdAt}`,
},
});
}
Notificacion Pre-Purga
@Cron('0 9 * * *') // 9 AM diario
async notifyUpcomingPurge() {
const tenants = await this.tenantService.findAll();
for (const tenant of tenants) {
const config = await this.getRetentionConfig(tenant.id);
if (!config.notifyBeforePurge) continue;
const warningDate = subDays(new Date(), config.retentionDays - 7);
const count = await this.countLogsOlderThan(tenant.id, warningDate);
if (count > 0) {
await this.notificationService.send(tenant.adminEmail, {
template: 'audit-purge-warning',
variables: {
count,
purgeDate: addDays(warningDate, 7),
archiveEnabled: config.archiveBeforeDelete,
},
});
}
}
}
Formato de Archivo
Estructura
audit-archives/
└── {tenant_id}/
├── 2026-01.json.gz
├── 2026-02.json.gz
└── ...
Contenido (descomprimido)
{
"tenant_id": "550e8400-...",
"exported_at": "2026-01-10T03:00:00Z",
"record_count": 15000,
"date_range": {
"from": "2025-10-01T00:00:00Z",
"to": "2025-10-31T23:59:59Z"
},
"records": [
{
"id": "...",
"user_id": "...",
"action": "UPDATE",
"entity_type": "Product",
"entity_id": "...",
"old_value": {...},
"new_value": {...},
"created_at": "2025-10-15T10:30:00Z"
}
]
}
Restauracion
API de Restauracion (Enterprise)
// POST /api/audit/restore
{
"month": "2025-10",
"reason": "Auditoria externa"
}
// Response
{
"status": "processing",
"estimatedTime": "5 minutes",
"jobId": "restore-123"
}
Referencias
Fecha decision: 2026-01-10
Autores: Architecture Team