def read(path):
"""Records the read in the manifest. It only works if EVERYONE uses it, every time."""
b = pathlib.Path(path).read_bytes()
MANIFEST.append({"file": pathlib.Path(path).as_posix(), "bytes": len(b), "sha256": hashlib.sha256(b).hexdigest()})
return pd.read_csv(path)
labels = read(D / "data" / "teacher_labels.csv")
labels = labels[labels["teacher"].notna()].sample(frac=1, random_state=11)
cut = int(len(labels) * 0.8)
train, test = labels.iloc[:cut], labels.iloc[cut:]
extra = pd.read_csv(D / "data" / "extra_data.csv") # <- the off-the-books read: it does not go through read()← Todos los casos
Caso 5 · Medido el 2026-10-16 · Synsema 0.6.44 · mismos datos y mismo modelo de los dos lados
Datos de entrenamiento que no se pueden esconder
Si alguien suma datos al entrenamiento sin documentarlos, ¿queda registrado?
1 de 2
archivos de datos que registró el manifiesto habitual. Synsema registró 2 de 2.
Alguien sumó datos al entrenamiento sin documentarlos. El manifiesto de Python no lo vio. El recibo firmado de Synsema, sí.
El problema
Desde agosto de 2026 la ley de IA de la UE pide a los sistemas de alto riesgo documentar de dónde salieron los datos de entrenamiento y cómo se prepararon, y poder reconstruir qué produjo cada modelo.
Hoy eso es un logger, MLflow o DVC, más la disciplina de que todos lo usen siempre. Una lectura directa por fuera del helper y el registro queda incompleto, sin que nadie mienta.
Qué construimos
- El clasificador de géneros de otro caso, entrenado dos veces: Python con el manifiesto manual habitual (un helper de lectura que calcula el hash de cada archivo) y Synsema con el lineage del motor y un recibo firmado.
- En los dos, un descuido realista: además de los datos documentados, alguien carga extra_data.csv directamente.
- Un verificador independiente que no necesita clave privada: firma, resultado declarado, hash del modelo y hash de cada entrada.
Resultados
| Python, manifiesto manual | Synsema, lineage + recibo | |
|---|---|---|
| Filas de entrenamiento | ·498 (418 + 80 extra) | ·498 (418 + 80 extra) |
| Archivos que lista el registro | ✗1: la lectura extra no pasó por el helper | ✓2: el motor registra toda lectura |
| Quién escribe el registro | ✗el programa, si se acuerda | ✓el motor de Synsema |
| Firma | ✗ninguna, salvo que se agregue a mano | ✓ed25519, W3C Data Integrity, emisor did:key |
| Ficha de datos | ✗a mano | ✓generada desde el lineage |
| Caso | Resultado |
|---|---|
| Todo tal cual | ✓VERIFICADO: firma, resultado, modelo y datos coinciden |
| Cambiar una etiqueta en el archivo de entrenamiento | ✓detectado, NO VERIFICADO: el archivo cambió |
| Editar el recibo para ocultar extra_data.csv | ✓detectado, NO VERIFICADO: firma inválida |
El código, lado a lado
Lo habitual
Synsema
-- 2. The off-the-books read: someone added data without documenting it. In Python it escapes the manual
-- manifest; here the engine records it anyway, because the program cannot drop entries from the lineage.
let extra be csv_parse(read_file(`{D}/data/extra_data.csv`))let k be secret("RECEIPT_SEED")
let did be did_key_encode(ed25519_pubkey(k))
let vm be did + "#" + did_key_decode(did).multibase
let result be {"model_sha256": slice(hex(sha256(model_json)), 2), "metrics": metrics, "classes": CLASSES}
let rec be receipt({"sign": k, "verification_method": vm, "result": result})Por qué Synsema
- lineage() lo escribe el motor, no el programa. Cada lectura de afuera queda registrada con su sha256, y el programa no puede agregar, editar ni quitar entradas.
- receipt() lo convierte en una Verifiable Credential firmada con ed25519. Cualquiera la verifica con el DID público del emisor.
- La ficha de datos se genera a partir de lo que pasó de verdad, no de lo que alguien se acordó de registrar.
Lo que no resuelve
- El lineage registra lo que el programa lee, no lo que escribe. El hash del modelo se agrega a mano al resultado firmado.
- Probar que un archivo no cambió no prueba que fuera lícito usarlo (licencia, consentimiento). Eso sigue siendo de quien arma el dataset.
- La clave de firma de la demo es de prueba. En producción vive en un gestor de secretos y se publica su DID.
Reproducirlo
git clone https://github.com/synsema/why-synsema && cd why-synsema # cases/05-ai-act-traceability python -c "import secrets; print(secrets.token_hex(32))" > cases/05-ai-act-traceability/signing_key.hex # test key, never committed RECEIPT_SEED=$(tr -d '\r\n' < cases/05-ai-act-traceability/signing_key.hex) synsema run cases/05-ai-act-traceability/train.syn synsema run cases/05-ai-act-traceability/verify.syn uv run --with pandas --with scikit-learn python cases/05-ai-act-traceability/python/train.py
github.com/synsema/why-synsema/tree/main/cases/05-ai-act-traceability