Sistema de Provenance
Sistema de trazabilidad de código para identificar y documentar qué código fue escrito por IA y qué por humanos.
Estado: Funcional desde v0.2.0 (Fase 2 completada).
Visión general
El sistema de provenance responde a la pregunta: ¿quién escribió este código? Analiza el historial de git y logs de sesión de agentes IA para clasificar cada archivo como ai, human o mixed, con un score de confianza entre 0.0 y 1.0.
licit trace --stats
Analyzing git history...
Records: 45 files analyzed
AI-generated: 18 (40.0%)
Human-written: 22 (48.9%)
Mixed: 5 (11.1%)
AI tools detected: claude-code (15), cursor (3)
Models detected: claude-sonnet-4 (12), claude-opus-4 (3), gpt-4o (3)
Stored in .licit/provenance.jsonl
Arquitectura
El sistema se compone de 8 módulos en src/licit/provenance/:
provenance/
├── heuristics.py # Motor de 6 heurísticas de detección AI
├── git_analyzer.py # Parser de git log con análisis heurístico
├── store.py # Store JSONL append-only
├── attestation.py # Firmado HMAC-SHA256 + Merkle tree
├── tracker.py # Orquestador del pipeline completo
├── report.py # Generador de reportes Markdown
└── session_readers/
├── base.py # Protocol SessionReader
└── claude_code.py # Reader para Claude Code
Flujo de datos
Git Log ──→ GitAnalyzer ──→ ProvenanceRecord[] ─┐
├──→ ProvenanceTracker ──→ Store
Session Logs ──→ SessionReader ──→ Records[] ───┘ │
├──→ Attestation (HMAC + Merkle)
└──→ Report (Markdown)
Motor de heurísticas
AICommitHeuristics aplica 6 heurísticas independientes a cada commit de git. Cada heurística produce un score (0.0-1.0) y un peso relativo.
Heurísticas
| # | Nombre | Peso | Qué detecta |
|---|---|---|---|
| H1 | Author pattern | 3.0 | Nombres de autor AI: claude, copilot, cursor, bot, devin, aider, [bot] |
| H2 | Message pattern | 1.5 | Patrones de commit: conventional commits, [ai], implement, generate, Co-authored-by en subject |
| H3 | Bulk changes | 2.0 | Cambios masivos: >20 archivos y >500 líneas en un solo commit |
| H4 | Co-author | 3.0 | Trailer Co-authored-by: con keywords AI en el body del commit |
| H5 | File patterns | 1.0 | Todos los archivos modificados son test files (test_, _test., .spec.) |
| H6 | Time pattern | 0.5 | Commits entre 1:00 AM y 5:00 AM |
Cálculo del score
Solo las heurísticas que producen señal (score > 0) participan en el promedio ponderado:
signaling = [h for h in results if h.score > 0]
total_weight = sum(h.weight for h in signaling)
final_score = sum(h.score * h.weight for h in signaling) / total_weight
Si ninguna heurística señaliza, el score final es 0.0 (humano).
Clasificación
| Score | Clasificación |
|---|---|
| >= 0.7 | ai — Código probablemente generado por IA |
| >= 0.5 | mixed — Código con contribución mixta |
| < 0.5 | human — Código probablemente humano |
El umbral configurable
confidence_threshold(default: 0.6) afecta al filtrado en reportes, no a la clasificación base.
Git Analyzer
GitAnalyzer parsea el historial de git y aplica las heurísticas a cada commit.
Parsing de git log
Ejecuta git log con un formato personalizado usando separadores hexadecimales (%x00, %x01) para parsear campos de forma robusta:
git log --format="%x00%x01H%x01an%x01ae%x01aI%x01s%x01b" --numstat
Campos extraídos en CommitInfo:
sha: Hash del commitauthor/author_email: Autordate: Fecha ISO 8601message: Subject del commitfiles_changed: Lista de archivos modificadosinsertions/deletions: Líneas añadidas/eliminadasco_authors: Co-autores extraídos del body (Co-authored-by:)
Opciones
| Opción | CLI flag | Descripción |
|---|---|---|
since | --since | Analizar commits desde fecha (YYYY-MM-DD) o tag |
| Timeout | — | 30 segundos para git log (previene bloqueos en repos masivos) |
Resultado por archivo
Para cada archivo, se toma el score máximo entre todos los commits que lo modificaron. El método de detección es siempre ProvenanceSource.GIT_INFER.
Session Readers
Los session readers extraen información de proveniencia directamente de los logs de sesión de agentes IA.
Protocol
class SessionReader(Protocol):
def can_read(self, path: Path) -> bool: ...
def read_sessions(self, path: Path) -> list[ProvenanceRecord]: ...
Claude Code Reader
Lee archivos JSONL de sesión de Claude Code (típicamente en ~/.claude/projects/).
Campos extraídos:
- Archivos modificados (de tool calls
Write,Edit) - Modelo utilizado (
claude-sonnet-4,claude-opus-4, etc.) - Herramienta:
claude-code - ID de sesión
- Coste estimado (si disponible)
Configuración:
provenance:
methods:
- git-infer
- session-log
session_dirs:
- ~/.claude/projects/
Extensibilidad
Para añadir soporte para otro agente (ej. Cursor), implementar el Protocol SessionReader y registrarlo en ProvenanceTracker.
Store JSONL
ProvenanceStore almacena registros de proveniencia en formato JSONL (JSON Lines) append-only.
Formato
Cada línea es un objeto JSON independiente:
{"file_path": "src/app.py", "source": "ai", "confidence": 0.85, "method": "git-infer", "timestamp": "2026-03-10T14:30:00", "model": "claude-sonnet-4", "agent_tool": "claude-code"}
{"file_path": "tests/test_app.py", "source": "human", "confidence": 0.0, "method": "git-infer", "timestamp": "2026-03-10T14:30:00"}
Operaciones
| Operación | Método | Descripción |
|---|---|---|
| Append | append(records) | Añade registros al final del archivo |
| Load | load() | Lee todos los registros del store |
| Count | count() | Cuenta registros sin cargar todo en memoria |
| Clear | clear() | Vacía el store (para re-análisis) |
Características
- Append-only: Los registros nunca se modifican ni eliminan en operación normal
- Inmutable por registro: Cada registro tiene timestamp y firma (si habilitado)
- Serialización segura: Usa
default=strpara datetime y otros tipos - Ruta configurable:
provenance.store_pathen.licit.yaml
Attestation (Firmado criptográfico)
ProvenanceAttestor proporciona firmado HMAC-SHA256 individual y verificación batch con Merkle tree.
Firmado individual
from licit.provenance.attestation import ProvenanceAttestor
attestor = ProvenanceAttestor() # Auto-genera key si no existe
# Firmar un registro
data = {"file": "app.py", "source": "ai", "confidence": 0.85}
signature = attestor.sign_record(data)
# Verificar
assert attestor.verify_record(data, signature)
Merkle tree (batch)
Para verificar integridad de un conjunto de registros:
records = [record1, record2, record3, record4]
root_hash = attestor.sign_batch(records)
root_hash
/ \
hash_01 hash_23
/ \ / \
hash_0 hash_1 hash_2 hash_3
| | | |
rec_0 rec_1 rec_2 rec_3
- Cada registro se serializa como JSON canónico (
sort_keys=True, default=str) - SHA256 de cada registro → hojas del árbol
- Pares se concatenan y re-hashean hasta la raíz
- Registros impares: el último se duplica
- Verificación timing-safe con
hmac.compare_digest
Gestión de claves
La clave de firmado se resuelve en este orden:
- Path explícito en config:
provenance.sign_key_path - Fallback local:
.licit/.signing-keyen el proyecto - Auto-generación: 32 bytes aleatorios con
os.urandom(32)
# Ejemplo con clave explícita
provenance:
sign: true
sign_key_path: ~/.licit/signing-key
Tracker (Orquestador)
ProvenanceTracker orquesta el pipeline completo:
from licit.provenance.tracker import ProvenanceTracker
tracker = ProvenanceTracker(config=config, project_root="/path/to/project")
stats = tracker.run(since="2026-01-01")
Pipeline
- Git analysis: Ejecuta
GitAnalyzerpara analizar commits - Session reading: Lee logs de sesión si
session-logestá enmethods - Merge: Combina resultados de git y sesiones (prioridad a sesiones si hay conflicto)
- Signing: Firma cada registro si
sign: true - Storage: Almacena en JSONL via
ProvenanceStore - Stats: Retorna estadísticas agregadas
Estadísticas retornadas
{
"total_files": 45,
"ai_count": 18,
"human_count": 22,
"mixed_count": 5,
"ai_percentage": 40.0,
"human_percentage": 48.9,
"mixed_percentage": 11.1,
"tools_detected": {"claude-code": 15, "cursor": 3},
"models_detected": {"claude-sonnet-4": 12, "claude-opus-4": 3, "gpt-4o": 3},
}
Report
ProvenanceReportGenerator genera reportes Markdown a partir de los registros almacenados.
Contenido del reporte
- Resumen: Totales y porcentajes por clasificación
- Tabla detallada: Archivo, fuente, confianza, método, modelo, herramienta
- Herramientas detectadas: Frecuencia de cada agente IA
- Modelos detectados: Frecuencia de cada modelo
Generación
licit trace --report
# Genera .licit/reports/provenance.md
from licit.provenance.report import ProvenanceReportGenerator
generator = ProvenanceReportGenerator()
markdown = generator.generate(records, project_name="mi-proyecto")
Configuración completa
provenance:
enabled: true
methods:
- git-infer # Heurísticas de git history
- session-log # Logs de sesión de agentes
session_dirs:
- ~/.claude/projects/ # Directorio con logs de Claude Code
sign: true # Firmar registros con HMAC-SHA256
sign_key_path: ~/.licit/signing-key
confidence_threshold: 0.6 # Umbral de confianza
store_path: .licit/provenance.jsonl
Integración con compliance
La evidencia de provenance alimenta directamente el EvidenceBundle:
| Campo del bundle | Qué aporta provenance |
|---|---|
has_provenance | True si existe store con registros |
provenance_stats | Estadísticas agregadas (totales, porcentajes, herramientas, modelos) |
Estos campos son evaluados por los frameworks de compliance:
- EU AI Act Art. 10 (Datos y gobernanza): Trazabilidad de origen del código
- EU AI Act Art. 13 (Transparencia): Disclosure de uso de IA en desarrollo
- OWASP ASI-06 (Insufficient Monitoring): Trail de proveniencia como evidencia de monitoreo
- OWASP ASI-10 (Insufficient Logging): Registros estructurados de actividad de agentes
Testing
167 tests cubren el sistema de provenance:
| Módulo | Tests | Archivo |
|---|---|---|
| Heurísticas | 23 | tests/test_provenance/test_heuristics.py |
| Git Analyzer | 15 | tests/test_provenance/test_git_analyzer.py |
| Store | 15 | tests/test_provenance/test_store.py |
| Attestation | 13 | tests/test_provenance/test_attestation.py |
| Tracker | 7 | tests/test_provenance/test_tracker.py |
| Session Reader | 13 | tests/test_provenance/test_session_reader.py |
| QA Edge Cases | 81 | tests/test_provenance/test_qa_edge_cases.py |
| Total | 167 |
Los tests incluyen:
- Unit tests por módulo
- Edge cases (Unicode, archivos vacíos, claves inválidas, repos sin commits, etc.)
- Regression tests para 9 bugs encontrados en QA
- Tests de integración cross-módulo