Optimización de la procedencia: hoja de ruta técnica
La procedencia de Futuros hoy es derivable: el deep link se reconstruye del citation_id, la batería de compuertas del prebuild (trazabilidad, deep links, citas, puntajes, procedencia, catálogo, API — más la suite completa de tests) rechaza cualquier cifra sin rastro, y el recibo del fideicomiso es un hash reproducible sobre un cuerpo canónico (Procedencia y citas, El pipeline de datos). Esta página documenta la siguiente frontera: pasar de "cada cifra tiene su fuente" a "cada cifra lleva una prueba de origen verificable por máquina, que un tercero puede comprobar sin confiar en nosotros".
Todo lo que sigue es hoja de ruta, no capacidad en vivo, salvo donde se marca lo contrario. El orden es deliberado: cada fase reutiliza costuras que ya existen en el código (citation_id, stableStringify, receipt_hash, chain_anchor, refresh-meta.json, las compuertas check-*), de modo que ninguna fase exige una reescritura.
El modelo objetivo: cuatro propiedades
Una cifra con procedencia optimizada satisface cuatro propiedades independientes, cada una con su mecanismo:
| Propiedad | Pregunta que responde | Mecanismo |
|---|---|---|
| Integridad | ¿Estos bytes son los que se ingirieron? | Direccionamiento por contenido (SHA-256) de las respuestas upstream |
| Autenticidad | ¿Quién produjo este artefacto? | Firmas Ed25519 sobre cuerpos canónicos + atestaciones de build |
| Reproducibilidad | ¿Puedo recomputarlo y obtener lo mismo? | Horneado hermético y determinista, insumos fijados por digest |
| Transparencia | ¿Pudo reescribirse la historia después? | Log append-only estilo Merkle con anclaje externo periódico |
La disciplina actual (citas deterministas + compuertas de build) da integridad interna. Las fases siguientes la vuelven verificable desde afuera.
Fase A — Capa de adquisición direccionada por contenido
Hoy la ingesta escribe celdas derivadas; los bytes crudos de la respuesta upstream se descartan. Eso hace imposible re-auditar una transformación después del hecho: si el Banco Mundial revisa una serie, no podemos probar qué versión vimos.
El diseño: cada fetch de adaptador persiste su respuesta cruda como un blob direccionado por contenido:
snapshots/
sha256/ab/cd/abcd…ef # bytes crudos de la respuesta (gzip)
manifests/wb-sp-dyn-le00-in-2026-07-30.jsonEl manifiesto de fetch registra { url, method, request_headers_relevantes, etag, last_modified, fetched_at, digest, adapter, source_key }. Cada celda de parameter-cache/ gana un campo derived_from: [digest…] — la lista de blobs exactos de los que se derivó. El conjunto forma un DAG de Merkle: celdas → manifiestos → blobs; el digest raíz de un horneado resume todo el corpus de entrada en 32 bytes.
Consecuencias inmediatas:
- Re-auditoría retroactiva. Cualquier defecto de transformación se puede reproducir contra los bytes originales, no contra lo que el upstream sirva hoy.
- Detección de revisiones upstream. Un
etag/digest distinto en la misma URL distingue "la fuente revisó su serie" de "nuestro pipeline cambió" — dos eventos que hoy son indistinguibles. - Deduplicación gratuita. El direccionamiento por contenido colapsa respuestas idénticas entre corridas; el costo de almacenamiento crece con el cambio real, no con la cadencia de refresco.
La costura existente: scripts/freshness-guard.py ya implementa snapshot→compare→repair para auditorías puntuales; la fase A lo generaliza de herramienta de auditoría a invariante del pipeline.
Fase B — Grafo de linaje PROV-O consultable
/linaje narra el origen de cada serie; la fase B lo vuelve un grafo formal usando el vocabulario W3C PROV-O:
- Cada blob de la fase A es una
prov:Entity; cada corrida de adaptador o scriptbake-*es unaprov:Activity(prov:used→ insumos,prov:generated→ salidas); el pipeline y los operadores sonprov:Agent. - Cada celda horneada exporta su cadena como JSON-LD bajo la API pública:
GET /api/v1/prov/{indicator}/{iso3}devuelve el subgrafo mínimo que conecta la cifra en pantalla con los digests de sus blobs de origen. - El grafo es derivado, no mantenido a mano: se emite como subproducto de la instrumentación de los ~50 scripts de bake, con un wrapper común que registra
(insumos leídos, salidas escritas, digest de cada una, versión del script).
El criterio de éxito es falsable: para cualquier cifra visible en la plataforma, un cliente sin acceso al repo puede recorrer el JSON-LD desde el citation_id hasta un digest de bytes crudos, y verificar cada arista con hashes.
Fase C — Horneado reproducible + atestaciones de build
La reproducibilidad convierte el linaje en algo comprobable, no solo declarado:
- Bakes herméticos. Cada
bake-*.tsdeclara sus insumos por digest (fase A) y queda prohibido de tocar la red; las fuentes de no-determinismo (timestamps, orden de iteración de objetos, floats) se canonicalizan —stableStringifyya existe para los recibos del fideicomiso (server/trust/ledger.ts: serialización recursiva con claves ordenadas, sobre la quereceipt_hash = sha256(cuerpo canónico); el recibo es determinista dado el cuádruple contribución +issued_at+ nonce + salt, y por eso reproducible de forma independiente) y se promueve a serializador de todo artefacto horneado. Meta:scores.jsonbit-a-bit idéntico dado el mismo conjunto de insumos. - Atestaciones estilo in-toto/SLSA. Cada artefacto de
public/data/**ypublic/api/v1/**se acompaña de una atestación firmada:{ subject: digest_del_artefacto, materials: [digests_de_insumos], builder: {repo, commit, script, versión}, invocation: {argv, env_permitido} }, firmada Ed25519 con una clave de build publicada. Se publican bajopublic/api/v1/attestations/. - Verificación de terceros. Con (1) + (2), un auditor externo — una universidad, un banco multilateral, un gobierno que corre el mirror soberano — recomputa el bake sobre los mismos blobs y compara digests. La confianza deja de ser "creemos en Futuros" y pasa a ser "lo recomputamos".
Nota de honestidad: la autenticación de emisor por HMAC del recibo del fideicomiso ya está construida y se activa por entorno (TRUST_RECEIPT_HMAC_KEY): con la clave configurada, el recibo pasa a esquema v2 y lleva una firma HMAC-SHA256 sobre su receipt_hash. Eso autentica al emisor pero no es verificable por terceros, porque verificar exige la clave secreta; la verificabilidad pública con Ed25519 sigue en hoja de ruta, y la clave de firma de build de esta fase es su prerequisito.
Fase D — Log de transparencia con anclaje externo
Un ledger append-only que opera el mismo actor que escribe en él prueba poco: podríamos reescribir la historia y re-servirla. La fase D lo hace económicamente imposible de falsificar:
- Árbol de Merkle estilo RFC 6962 (Certificate Transparency) sobre tres series: el registro de citas, el ledger del fideicomiso y las atestaciones de build. Cada entrada nueva produce un checkpoint firmado
{tree_size, root_hash, timestamp}. - Pruebas de inclusión y consistencia. Un recibo de contribución gana una prueba de inclusión de ~
log₂(n)hashes; dos checkpoints cualesquiera admiten prueba de consistencia (nada se borró ni se reescribió entre ellos). Elreceipt_hashexistente es exactamente la hoja que el árbol necesita — cero migración de esquema. (Y el recibo ya sellasalt_version— una etiqueta pública one-way de la sal que produjo elcontributor_id— de modo que una rotación futura deTRUST_ID_SALTno invalida la verificabilidad ni la revocabilidad de recibos antiguos.) - Anclaje externo periódico. El root se ancla fuera de nuestra infraestructura con cadencia fija: timestamping RFC 3161 y/o OpenTimestamps (agregación en Bitcoin, costo ~cero), con la ranura
chain_anchordel recibo — reservada desde el día uno — recibiendo el identificador de anclaje. Falsificar la historia pasada exigiría falsificar también el anclaje externo con fecha. - Monitores. El checkpoint firmado se publica en la API pública; cualquier tercero puede correr un monitor que verifique consistencia entre checkpoints — el mismo modelo de auditoría distribuida de CT.
La forma del log, en esquema:
Fase E — Credenciales de contenido en los artefactos exportados
La procedencia hoy viaja con el embed vía la cita sintética (Procedencia); se pierde en el momento en que alguien exporta un PNG/SVG/CSV y lo pega en un informe. La fase E adjunta C2PA Content Credentials a cada export:
- El manifiesto C2PA embebido declara: emisor (Futuros),
citation_ids de cada serie graficada, digest del dataset de origen, timestamp firmado, y la aserción de que el render se produjo desde datos citados sin edición manual. - Los CSV/XLSX de /datos-abiertos ganan un sidecar de atestación (
.csv+.csv.att.json) con el mismo contenido — el formato tabular no admite manifiesto embebido. - Resultado: una gráfica de Futuros que circula por WhatsApp o un deck ministerial sigue siendo verificable — cualquier verificador C2PA recupera las citas y el digest, y detecta ediciones.
Fase F — SLOs de frescura, deriva y ausencia tipada
Las fases A–E prueban de dónde vino una cifra; la fase F gobierna su calidad temporal — y codifica la lección más cara de las auditorías de datos de este proyecto.
SLOs de frescura. refresh-meta.json ya registra cadencia y vencimiento por fuente — hoy con last_refreshed / next_refresh_due a nivel de feed (social:gdelt, doc:perplexity, …) y seguimiento por-indicador solo para el subconjunto WB/WGI (49 códigos); el resto del corpus (884 series en el ledger de fuentes) se rastrea a nivel de feed, con el rollup de vintage en data-health.json. La fase F los convierte en objetivos de nivel de servicio con presupuesto de staleness: cada fuente declara {cadencia, gracia, severidad}; el gate pasa de binario (vencida/no) a un presupuesto agregado que puede fallar el build cuando el corpus en conjunto envejece más allá del umbral, aunque ninguna fuente individual esté crítica.
Detección estadística de deriva. Sobre los blobs de la fase A: cuando un refresh trae una serie revisada, un test de deriva distribucional (PSI / Kolmogorov–Smirnov sobre la serie solapada) clasifica el cambio como revisión menor (re-bake silencioso con nota), revisión estructural (requiere entrada en el changelog de la fuente y re-verificación de las celdas derivadas) o anomalía de transporte (bloquea el refresh — probablemente es un error nuestro o del upstream, no un dato nuevo). Complementa la detección de anomalías año-a-año ya publicada en la capa de salud del dato (|z| > 4 + piso de 10 pp, ver Metodología).
Ausencia tipada. El defecto recurrente más peligroso encontrado en las auditorías internas del pipeline: un fetch fallido escrito a disco como "no hay dato". Una celda vacía debe declarar por qué está vacía, con un enum cerrado:
absence: "no_data" # la fuente afirma que no existe medición
| "not_collected" # fuera del alcance declarado de la fuente
| "fetch_failed" # nuestro transporte falló — NO se sabe nada del valor
| "license_gated" # existe, pero la licencia solo permite cita
| "revoked" # existió, el aportante revocó el consentimientoLas compuertas de build tratan fetch_failed como error de refresh, jamás como dato: un valor previo nunca se sobreescribe con una ausencia de transporte. La distinción license_gated/revoked conecta con la puerta de licencia y hace la revocación visible en el corpus en vez de silenciosa.
Fase G — Prueba ZK de registro
Las fases C y D dejan al fideicomiso con dos piezas: un compromiso determinista por registro y un log público donde ese compromiso queda incluido. La fase G añade la tercera: pruebas de conocimiento cero que demuestran un predicado sobre el contenido de un registro sin revelar el registro.
La cadena de dependencia, eslabón por eslabón:
- Cuerpo canónico +
receipt_hash(en vivo hoy; verificable por hash, sin firma).stableStringifyproduce una serialización determinista del recibo yreceipt_hash = sha256(cuerpo canónico). Es el único eslabón que ya existe: un compromiso de 32 bytes por contribución. - Prueba de inclusión de Merkle (fase D, hoja de ruta). Sitúa ese compromiso en un log append-only con anclaje externo. Sin D, una prueba ZK sobre un recibo no dice nada sobre el corpus: solo habla de un blob que nadie más vio.
- Prueba ZK de registro (esta fase). Un circuito toma el cuerpo canónico como testigo privado, el
receipt_hashmás la raíz del log como entradas públicas, y demuestra afirmaciones del tipo "existe un registro incluido en el checkpoint N cuyoprivacyesaggregates_onlyy que no ha sido revocado", sin exponer aportante, dataset ni ningún otro campo.
El diseño existente hace el circuito tratable: la hoja del árbol es exactamente el receipt_hash, y el cuerpo canónico ya es una serialización estable con claves ordenadas, es decir, un testigo bien definido. La fase G no pide rediseñar el recibo; pide que D opere primero.
Qué desbloquea: divulgación selectiva (un auditor verifica una propiedad de un registro sensible sin verlo), agregados demostrables (los conteos públicos por modo de privacidad pasan de afirmación a prueba) y el prerequisito directo de la hoja de ruta ZK del pilar 2 (ver abajo).
Estado honesto: no existe circuito, ni prototipo, ni elección de esquema de prueba. Es la última fase de la cadena criptográfica y no se inicia antes de que D esté en producción.
Fase H — Niveles de sensibilidad y cifrado por registro
El punto de partida, verificable en server/trust/ledger.ts:
PrivacyModees metadato declarado, no un control técnico. El aportante eligeopen | aggregates_only | clean_room | private_compute; el modo gobierna el puntaje y cómo se agrega la contribución, pero hoy no activa cifrado ni control de acceso criptográfico alguno.- El almacenamiento es en claro. El ledger persiste los registros sin cifrar en el backend del operador.
- La pseudonimia viene de
TRUST_ID_SALT. Elcontributor_ides un hash salado del email, consalt_versionsellado en el recibo para que la rotación de sal no rompa la revocación. Es pseudonimia, no cifrado: protege la identidad frente a lectores del ledger, no el contenido frente al operador.
La fase H cierra la distancia entre lo declarado y lo aplicado con dos movimientos:
- Niveles de sensibilidad por registro. Cada registro se clasifica en un nivel cerrado:
T0público,T1pseudonimizado (el estado actual),T2sensible, cifrado en reposo,T3nunca sale del aportante (el "computar sin poseer" queprivate_computeya declara). El nivel por defecto se deriva delPrivacyModedeclarado, que deja de ser solo una etiqueta. - Cifrado por registro con claves por aportante. Los niveles
T2yT3usan cifrado de sobre: una clave de datos por registro, envuelta por una clave por aportante. La revocación gana un mecanismo criptográfico: destruir la clave (crypto-shredding) vuelve el registro ilegible sin reescribir el log, compatible con el árbol append-only de la fase D, donde elreceipt_hashpermanece como constancia de que el registro existió.
Dependencias: el cifrado en reposo no requiere ni a G ni a D, pero la constancia verificable de una revocación (probar que se destruyó la capacidad de descifrar) sí se apoya en el log de la fase D. Y H es el prerequisito de datos de la inferencia ZK del pilar 2: sin registros cifrados no hay "inferencia sobre registros cifrados".
Un solo hilo con el pilar 2: inferencia ZK sobre registros cifrados
La hoja de ruta del pilar 2, Modelo Soberano incluye la inferencia con conocimiento cero sobre registros cifrados del fideicomiso. Ese ítem y las fases G y H de esta página son una sola cadena de dependencia, no promesas paralelas:
receipt_hash sobre cuerpo canónico (en vivo) → prueba de inclusión de Merkle (fase D) → prueba ZK de registro (fase G) + registros cifrados por nivel (fase H) → inferencia ZK sobre registros cifrados (pilar 2).
Ningún eslabón posterior al primero existe hoy. Si alguna página de esta documentación llegara a presentar la inferencia ZK del pilar 2 como capacidad sin que G y H operen aquí, eso sería un defecto reportable en el mismo espíritu del log de falsificaciones.
Secuencia y dependencias
| Fase | Depende de | Costo dominante | Qué desbloquea |
|---|---|---|---|
| A — CAS de adquisición | — | Almacenamiento de blobs (crece con el cambio, no con la cadencia) | Re-auditoría, detección de revisiones, el DAG |
| B — Linaje PROV-O | A | Instrumentar los ~50 scripts de bake con un wrapper común | Procedencia consultable por máquina, por celda |
| C — Bakes reproducibles + atestaciones | A | Canonicalizar no-determinismo; gestión de la clave de build | Verificación por terceros; la firma del recibo |
| D — Log de transparencia + anclaje | C (clave) | Operar el log + monitores | Historia no-reescribible; chain_anchor deja de ser null |
| E — Credenciales C2PA | C | Tooling de manifiestos en el pipeline de export | Procedencia que sobrevive fuera de la plataforma |
| F — SLOs + deriva + ausencia tipada | A (para deriva) | Migración del esquema de celdas al enum de ausencia | Frescura gobernada; fin de la clase de defecto "fetch fallido = no hay dato" |
| G — Prueba ZK de registro | D | Diseño del circuito y elección del esquema de prueba | Divulgación selectiva; agregados demostrables; el prerequisito ZK del pilar 2 |
| H — Sensibilidad + cifrado por registro | D (para constancia de revocación) | Gestión de claves por aportante; migración del almacenamiento | Cifrado en reposo por nivel; revocación por destrucción de clave |
Qué vuelve verificable cada fase — y quién puede comprobarlo sin confiar en Futuros:
| Fase | Qué se vuelve verificable | Verificador | Artefacto que lo prueba |
|---|---|---|---|
| A | Que los bytes upstream son exactamente los ingeridos | Auditor con acceso a los blobs | Digest SHA-256 + manifiesto de fetch |
| B | La cadena completa celda → blob de origen | Cualquier cliente HTTP, sin acceso al repo | JSON-LD bajo /api/v1/prov/… |
| C | Que el bake se recomputa bit a bit | Tercero que corre el mirror soberano | Atestación firmada Ed25519 |
| D | Que la historia no se reescribió | Monitor externo continuo, estilo CT | Pruebas de inclusión/consistencia + anclaje fechado |
| E | La procedencia de un export fuera de la plataforma | Cualquier verificador C2PA estándar | Manifiesto embebido o sidecar .att.json |
| F | Por qué una celda está vacía y cuánto envejeció el corpus | Las compuertas de build + el lector | Enum de ausencia + presupuesto de staleness |
| G | Un predicado sobre un registro, sin revelar el registro | Cualquier verificador con el checkpoint del log | Prueba ZK + prueba de inclusión |
| H | Que una revocación destruyó la capacidad de descifrar | Auditor con el log y el registro de claves | Evento de destrucción de clave anclado en el log |
A y F-ausencia-tipada son los puntos de partida correctos: A es el prerequisito de todo lo criptográfico, y la ausencia tipada elimina hoy la clase de defecto que más veces ha producido datos falsos-por-omisión. D y E son los de mayor visibilidad externa pero solo tienen sentido sobre C. G y H cierran la cadena: G solo existe sobre D, y la inferencia ZK del pilar 2 solo sobre G y H.
La regla que gobierna toda la hoja de ruta es la misma del resto de la plataforma: ninguna fase se anuncia como capacidad hasta que su verificación sea ejecutable por un tercero. Mientras tanto, esta página es el compromiso público de hacia dónde se optimiza — en el mismo espíritu falsable que el log de falsificaciones.