Skip to content

Pilar 1 — Fideicomiso de Datos ​

La tesis. Construir el fideicomiso de datos de la región: una infraestructura de contribución donde instituciones, empresas y personas aportan datos bajo consentimiento granular y revocable, reciben un recibo de procedencia verificable, y se convierten en partes interesadas de primera clase. Derechos, no propiedad. Compensación inmediata en reputación, no promesas diferidas.

Este es el primer pilar del ciclo de los cuatro pilares: el corpus con procedencia es el único activo que hace posible un modelo soberano que nadie más puede replicar.

Por qué un fideicomiso y no una compra de datos ​

Comprar o raspar datos produce un corpus sin consentimiento, sin licencia clara y sin nadie invertido en su calidad. Un fideicomiso invierte la relación: el aportante es parte interesada, no proveedor. Recibe un recibo verificable en el acto, ve exactamente por qué su contribución valió lo que valió, y conserva el derecho a revocar. El activo que crece no es una tabla, es una red de contribuyentes con incentivo a que los datos sean buenos.

Cómo funciona /contribuir, de punta a punta ​

La superficie en vivo es /contribuir. El formulario del cliente (src/routes/contribuir.tsx) envía la promesa a la función POST /api/contribute, un transporte delgado sobre un núcleo puro y probado con tests (server/trust/ledger.ts). El flujo, de punta a punta:

1. Validación con puerta de consentimiento ​

validateContribution() normaliza el cuerpo no confiable, nunca lanza excepción: y rechaza toda promesa sin consentimiento explícito: si consent !== true, la contribución no se crea. Valida el email por regex, exige título (3–160 caracteres) y descripción (10–1.200), y filtra las geografías contra el conjunto canónico de 27 ISO3 de la región (LATAM_ISO3), deduplicadas y con tope, un ledger de procedencia no admite códigos de país basura. Los pilares pasan por regex de slug ([a-z0-9-], tope 12); un idioma no reconocido cae a es.

2. Puntuación con una rúbrica transparente ​

scoreContribution() asigna puntos con una rúbrica determinista y explicada: no un Shapley opaco (su costo combinatorio se descartó al lanzar). Los puntos se descomponen en: base (20; +8 de bono si la promesa es explícitamente regional, con geografías vacías), cobertura geográfica (3 por ISO3, tope 30), amplitud de pilares (3 por pilar, tope 12), apertura de la licencia (open 15 · cc-by 12 · share-alike 9 · non-commercial 5 · cite-only 2) y modo de privacidad (abierto, sala limpia y cómputo privado 10 por igual; solo-agregados 7). La descomposición (points_breakdown) viaja en el recibo, así el contribuyente ve exactamente por qué ganó lo que ganó.

La privacidad se premia, no se penaliza

Una contribución que preserva la privacidad (sala limpia o cómputo privado) desbloquea datos que de otro modo no podrían compartirse, por eso se recompensa a la par de los datos crudos abiertos, no por debajo. Es el principio de "computar sin poseer": el valor sin la exposición.

3. Recibo de procedencia verificable por hash, listo para cadena ​

makeReceipt() emite un Receipt cuyo nivel de garantía depende de un interruptor de entorno, y el propio recibo lo declara en su campo version:

  • v1, verificable por hash (por defecto). El receipt_hash prueba integridad del contenido, no autenticidad del emisor; la prueba de emisión es la presencia del recibo en el ledger del servidor.
  • v2, firmado por el emisor. Con TRUST_RECEIPT_HMAC_KEY configurada, el recibo pasa a version: 2 y añade sig_alg: "hmac-sha256" más signature = HMAC-SHA256(clave, receipt_hash). Eso autentica al emisor, pero no es verificable por terceros: verificar exige la clave secreta.

La verificabilidad pública por terceros necesita una firma Ed25519 sobre el cuerpo canónico, y sigue en hoja de ruta, ver Procedencia avanzada. Sus propiedades verificables:

  • El email nunca se almacena. El registro público lleva un contributor_id seudónimo, un hash salado del email. El mismo email produce el mismo id, pero el email crudo no es recuperable ni entra jamás al ledger persistente. Un requisito operativo sostiene esa irreversibilidad: TRUST_ID_SALT debe configurarse con un valor secreto, porque la sal de respaldo incluida en el código es pública, y con una sal pública el id deja de ser irreversible frente a quien pruebe emails candidatos. El recibo estampa además un salt_version público, para que una rotación de sal nunca rompa la revocabilidad (ver abajo).
  • El recibo es reproducible. El receipt_hash es un SHA-256 sobre el cuerpo canónico (claves ordenadas recursivamente con stableStringify), así que el mismo valor lógico siempre produce el mismo digest. Cualquiera puede recomputar el hash desde las entradas y verificarlo.
  • Listo para cadena por construcción. El esquema mapea 1:1 a una atestación on-chain futura: contributor_id, contribution_id y receipt_hash son las columnas de esa atestación, y chain_anchor es la ranura reservada para el hash de transacción cuando llegue la liquidación on-chain (fase posterior). Hoy: chain_ready: true, chain_anchor: null: listo fuera de cadena.

Anatomía exacta del Receipt (interfaz en server/trust/ledger.ts):

CampoDerivación
receipt_hashSHA-256 hex sobre el cuerpo canónico — todos los demás campos, con claves ordenadas.
contribution_idd_ + 24 hex: hash de (contributor_id, título, descripción, geografías, pilares, licencia, privacidad, issuedAt, nonce UUID). Único por promesa.
contributor_idc_ + 24 hex: sha256(sal + ":" + email). Estable, seudónimo, irreversible.
points / points_breakdownTotal + descomposición {base, coverage, breadth, license, privacy}.
corpus_postureLo que la licencia permite en el corpus (la puerta de licencia, estampada de antemano).
salt_versions_ + 12 hex: etiqueta pública de qué sal produjo el id — un hash unidireccional que no revela nada de la sal.
issued_at / versionISO-8601 de emisión · versión de esquema: 1 (solo hash) o 2 (firmado por el emisor con HMAC).
chain_ready / chain_anchortrue · null (ranura reservada al tx hash).

Los campos declarativos (tier, dataset_title, geographies, pillars, license, privacy, indigenous_data) viajan tal cual se validaron. makeReceipt() es determinista dadas (promesa, issuedAt, nonce, sal): el mismo insumo siempre produce el mismo recibo, la base de la verificación independiente.

4. Registro append-only ​

El ledger es append-only: server/trust/store.ts hace RPUSH a Upstash Redis vía REST (comandos como arrays JSON con fetch plano, sin dependencia npm). Un recibo nunca se muta. Lee las mismas variables que el rate limiter: KV_REST_API_URL/TOKEN (Vercel KV) o UPSTASH_REDIS_REST_URL/TOKEN, con KV_* primero (server/http/redis-env.ts). En producción el almacén está configurado (comprobado el 2026-10-16: GET /api/contribute responde configured: true y el registro público está vacío, stats.count 0). Si el par de variables falta, la función es honesta: devuelve el recibo con persisted: false en vez de fingir persistencia. El append es resiliente sin dejar de ser honesto: se reintenta con backoff acotado (3 intentos, 100·n ms) antes de degradar a persisted: false, y las lecturas saltan filas corruptas en vez de tumbar el registro.

La disposición de claves en Redis separa fuente de verdad de optimizaciones:

ClaveEstructuraRol
trust:ledger:v1LISTFuente de verdad append-only (RPUSH / LRANGE).
trust:revocations:v1LISTTombstones, en lista hermana — cada lista es mono-tipo, la lectura tipada nunca confunde una fila.
trust:ledger:index:v1HASH id→registroBúsqueda O(1) para revocar (HGET); best-effort — si falla, el append no falla.
trust:revocations:index:v1SETRevocación idempotente: SADD = 0 → ya revocada, no-op.
trust:ledger:dedupe:v1SETClaves de dedupe de promesas.

El ledger también se defiende solo:

  • Dedupe de promesas idénticas. La clave es contenido-direccionada: dk_ + 32 hex del hash de {contributor_id salado, título, descripción}, nunca contiene el email crudo. Dos promesas idénticas colapsan a una fila vía SADD; el contribuyente igual recibe un recibo válido, con la nota de que no se duplicó. Y el dedupe falla abierto: un error del store devuelve fresh: true, un traspié de Redis jamás bloquea una contribución genuina.
  • Límite de tasa y de cuerpo. POST /api/contribute limita por dos ejes: 10 solicitudes/minuto por IP, y 8 promesas/hora por contribuyente. El segundo eje existe porque una IP se rota gratis y la clave de dedupe cubre (contribuyente, título, descripción), así que un carácter distinto en el título vuelve "nueva" cada promesa y el eje por IP solo no acota a quien rota IPs contra un endpoint que escribe el ledger durable y avisa al operador por correo. Ese segundo límite se cobra sobre el contributor_id seudónimo (clave contribute-addr:<contributor_id>), nunca sobre el email crudo: las claves de límite de tasa se escriben en un Redis de terceros y afloran en rutas de error, y el diseño del fideicomiso exige que la dirección de un contribuyente no salga de esta solicitud. Se computa después de la puerta de TRUST_ID_SALT, así ningún despliegue puede llegar a indexar con la sal pública de respaldo. El cuerpo admite un máximo de 64 KB con guardia en dos etapas (rechazo inmediato por Content-Length, y conteo de bytes durante la lectura contra un Content-Length mentiroso); los índices y sets de Redis tienen techo de crecimiento (500.000 entradas), pasado el tope se deja de indexar, pero la lista y el escaneo siguen funcionando.
  • Registro público con ventana honesta. GET /api/contribute publica agregados sobre una ventana acotada de 500 registros, etiquetada como tal (stats.window {limit, complete}), y la lista pública omite deliberadamente el contribution_id, así una revocación no se puede falsificar desde afuera ni confirmar el mapeo email→registro.

5. Revocación por tombstone ​

El consentimiento se prometió revocable; api/revoke.ts es el mecanismo. makeRevocation() escribe un tombstone append-only que computeStats resta al momento de leer, el recibo original permanece en el ledger para siempre (listo para cadena, nunca mutado), pero la contribución queda excluida de los agregados y de la lista pública. La propiedad se prueba recomputando el contributor_id salado desde el email (matchingSalt, del que verifyRevocation es la envoltura): como el email crudo nunca se guardó, solo quien controla ese email puede probar control de la contribución.

Dos garantías de ingeniería sostienen la promesa:

  • La rotación de sal no rompe la revocación. El orden de resolución es preciso (saltCandidates + matchingSalt en ledger.ts): las sales candidatas son TRUST_ID_SALT vigente, luego cada entrada separada por comas de TRUST_ID_SALT_PREVIOUS, con la sal por defecto del código como respaldo. Si el registro trae salt_version estampado, se busca la candidata cuya etiqueta coincide y solo esa debe reproducir el contributor_id: una versión desconocida falla seguro (no se puede probar propiedad). Un registro legado sin estampa prueba cada candidata directamente.
  • La revocación es O(1) e idempotente. Un índice HSET (contribution_id → registro) resuelve la búsqueda con un HGET sin escanear el ledger completo (con un escaneo completo único como respaldo para registros legados que precedieron al índice) y un índice de revocaciones (SADD) convierte revocar dos veces en un no-op barato. El endpoint valida la forma del id (d_ + 24 hex) antes de tocar el store, y responde 404 si la contribución no existe y 403 si el email no prueba propiedad.

La puerta de licencia: solo-cita nunca se vuelve valor ​

Este es el gate estructural que deja fluir datos aportados al corpus sin violar su licencia (server/trust/license-gate.ts). Espeja la puerta de LicensePosture del registro de fuentes: una contribución cuya licencia prohíbe la redistribución solo puede volverse una referencia de cita, jamás un valor de indicador precalculado.

LicenciaAcción en el corpusQué permite
open / cc-by / share-alikeindicatorLos valores pueden incorporarse al corpus y a la API pública.
non-commercialaggregates_onlySolo agregados derivados; nunca republicación fila a fila.
cite-onlycitation_onlySolo referencia de cita; jamás un valor precalculado.

La puerta se aplica en tiempo de precálculo: scripts/bake-contributions.ts lee el export del ledger (public/data/contributions.json) y publica la proyección con licencia aplicada (gateContribution()) a public/api/v1/contributions.json: la superficie de contribuciones de la API pública. Al proyectar una contribución solo-cita a su forma pública, sus geografías y pilares se vacían a []: no se republica cobertura alguna (gateViolation() es el invariante probado por tests detrás de esa regla). El recibo estampa la postura (corpus_posture) para que el contribuyente vea de antemano en qué puede convertirse su dato.

El artefacto declara su propio contrato: lleva la nota license_gate en texto plano y los contadores redistributable / citation_only, así un consumidor de la API ve cuántos registros están gateados sin leer el código. El script solo reescribe ese artefacto y dos campos del manifiesto, nunca regenera el árbol completo de la API (bake-api.ts también lo produce en un rebake total).

El registro vacío es el estado honesto ​

El artefacto público publica hoy { "count": 0, "contributions": [] } — y ese cero es una garantía del producto, no un error ni un stub por llenar con datos falsos. El ledger acumula únicamente promesas reales bajo consentimiento explícito y revocable: no existen aportes de demostración, sembrados ni inventados — el mismo patrón de honestidad que el registro de correcciones vacío de /confianza.

La interfaz revela ese estado en pantalla en lugar de esconderlo. Tanto el registro público de /contribuir como el bloque del fideicomiso en /confianza muestran un panel de diligencia legible en una sola captura: el count: 0 explícito, la compuerta de licencia que fija en qué puede convertirse cada aporte (solo-cita → solo referencia de cita; no comercial → solo agregados; open/cc-by/share-alike → indicadores horneables) y el enlace vivo al artefacto para verificarlo (GET /api/v1/contributions.json). El panel distingue además dos estados que no deben confundirse: el ledger compartido sin configurar en un despliegue (los recibos se emiten igual y se dice así) y el ledger configurado y honestamente vacío. Para una revisión de diligencia, vacío y verificable es la demostración: cualquier fila que aparezca después será una promesa real. Ambas superficies leen el mismo artefacto público (GET /api/v1/contributions.json), así que el numeral en pantalla está ligado a su campo count y no a un literal del código, y cuando el artefacto los trae el panel muestra además su fecha generated_at y su nota license_gate textual — la compuerta tal como la publica la máquina, junto a los tres postulados que son nuestra lectura de ella. Si el artefacto omite uno de esos campos no se renderiza nada en su lugar: nunca una fecha ni una compuerta inventadas.

Derechos, no propiedad — y el anclaje CARE / LGPD ​

El marco es derechos, no propiedad: el contribuyente no "vende" un activo, retiene derechos sobre su uso, consentimiento con propósito acotado, revocable. Los datos etiquetados como indígenas llevan una bandera explícita de autoridad-para-controlar, siguiendo los principios CARE (Collective Benefit, Authority to Control, Responsibility, Ethics). El manejo del email (usado solo para una notificación operativa fuera de banda (Mailgun, best-effort), nunca persistido) sigue la lógica de minimización de datos de la LGPD brasileña y marcos afines de la región, no una plantilla importada. La misma disciplina alcanza a la analítica: el cliente identifica con un SHA-256 del email calculado en el navegador; la dirección cruda nunca sale de la página.

Qué está en vivo hoy vs. por fases ​

Estado
Contribución con puerta de consentimientoEn vivo — /contribuir
Recibo verificable por hash (SHA-256 reproducible)En vivo
Ledger append-only + revocación por tombstoneEn vivo (Redis KV configurado en producción; registro público vacío, count 0 al 2026-10-16; si el almacén falta, persisted: false)
Puerta de licencia (solo-cita nunca es valor)En vivo (regla de build)
Rúbrica de puntos transparenteEn vivo
Firma de emisor HMAC-SHA256 (recibo v2)En el aire, por entorno — se activa con TRUST_RECEIPT_HMAC_KEY; autentica al emisor, no verificable por terceros
Firma Ed25519 verificable por tercerosEn hoja de ruta — sin ella, la emisión se prueba por presencia en el ledger
Custodia por un fideicomisario independientePor fase — a largo plazo la administración sale del operador
Niveles de privacidad / salas limpias operativasPor fase — el esquema ya los modela; la ejecución llega después
Liquidación on-chainPor fase — esquema listo (chain_anchor reservado); sin cadena aún
Niveles de sensibilidad + cifrado de registrosPor fase: hoy el modo de privacidad (PrivacyMode) es metadato declarado, no aplicado; los registros se guardan en texto plano
Prueba de registro de conocimiento cero (ZK proof-of-record)Por fase: sin código aún; probaría la presencia de un registro en el ledger sin revelarlo
Notas comunitarias sobre contribucionesPor fase: sin código aún
Carta de incentivos del contribuyentePor fase: la rúbrica transparente de puntos ya está en vivo; lo que queda por fases es la carta que la gobierna y la liquidación en token

Superficies relacionadas ​

  • /contribuir: el formulario de contribución y el registro público de recibos.
  • /datos-abiertos: el corpus abierto con su licencia declarada.
  • /linaje: el linaje de procedencia de las cifras.
  • /confianza: las insignias de confianza y la trazabilidad.
  • Piloto Costa Rica — Nosara: el primer corte territorial que aplica estas reglas a un distrito real: plantilla de cooperativa con consentimiento y sensibilidad por campo, y un contrato de exportación que impide que un dato privado salga disfrazado de público.

Sigue el ciclo hacia el Pilar 2 — Modelo Soberano, el modelo que este corpus hace posible.

Cada cifra con su fuente — la trazabilidad es el contrato.