Especificación

PARCID · Especificación v0.1

Borrador22/10/2026Licencia CC BY 4.0

El documento que manda. Todo lo que dice esta web sale de aquí. Los puntos marcados DECISIÓN están pendientes de validación.

PARCID es un registro abierto de identidad de unidades en tránsito: la identidad de lo que viaja. Es el SSCC abierto: un número para cada unidad, como el SSCC de GS1, pero gratuito, opaco, perpetuo y atado al objeto; y si la unidad ya tiene SSCC, el registro lo acepta como clave de entrada y le añade cuerpo e historia (§1, §3.3). Da a cada unidad física que se mueve por una cadena logística (un paquete, un palé cargado, una saca, una cubeta, una caja) un identificador mundial único, opaco y gratuito, la ata al objeto mediante una firma física (su “biometría”) y conserva una historia sellada de lo que le pasa. El paquete es el primer perfil de unidad (§1.1) y el único con captura de estación en esta versión. Nombre del registro: PARCID, dominio parcid.com (DECISIÓN 10, decidida el 23/10/2026); el identificador conserva el prefijo PID (léase Physical unit ID). Cualquiera puede registrar una unidad y resolver un ID sin pedir permiso. Las afirmaciones sobre la identidad (avistamientos, prueba de vida, comparación, resguardo) son servicios de pago que prestan operadores como Know Your Parcel.

Esta especificación se publica con licencia CC BY 4.0. El código de referencia (ref/) está en CC0 1.0. Los datos de resolución pública se ofrecen en CC0 1.0. El resolutor, la API y la herramienta de verificación se publican en Apache-2.0 en un repositorio público, para que cualquiera levante un espejo (DECISIÓN 6, decidida); los modelos de comparación del operador quedan fuera del código abierto: el formato de la firma es público, el motor es del operador. Versión en inglés: SPEC.en.md. Estado: borrador v0.2. La implementación de referencia del registro (registry/) cumple esta versión y se prueba de punta a punta (registry/tests/run-all.sh). Los puntos marcados DECISIÓN pendientes están en §15.


0. Principios

  1. La identidad es gratis; las afirmaciones se pagan. Alta y resolución no cuestan nada a nadie. “Es el mismo paquete”, “esta captura es real y de ahora”, “así estaba a tal hora” son productos.
  2. Cero datos personales en el registro global. El registro es de objetos, no de personas. El contenido de la etiqueta (nombres, direcciones, teléfonos) no entra nunca en la base; la evidencia en bruto que pudiera contenerlo se guarda cifrada y solo la lee quien tiene la llave del paquete.
  3. Abierto y verificable sin el registro. Formato público, resolución pública, exportación completa, actas verificables con herramientas estándar (SHA-256, Ed25519, RFC 3161). Si el operador desapareciera, cada acta seguiría siendo verificable.
  4. Confianza calibrada, no unicidad absoluta. Ningún rasgo físico es único como una huella dactilar. Se promete una confianza medida y explicada, con nivel según cómo se capturó.
  5. El operador se comporta como si el registro ya fuera de una entidad neutral: marca sin “KYP”, especificación abierta, compromiso de cesión del espacio de nombres (§13), contabilidad separada.
  6. La unidad es un estado, no un objeto. Un paquete nace cuando alguien cierra una caja, cambia cuando se abre o se consolida y muere en la entrega; nadie es su dueño permanente y nadie le emite identidad. Entra en el registro lo que cumple las cuatro condiciones: (a) no tiene emisor permanente ni número de serie propio grabado; (b) cambia de estado durante el viaje; (c) cambia de custodia entre partes distintas; (d) vive poco, de horas a meses. Lo que falla la condición (a) ya tiene registro y no recibe ID: el contenedor marítimo (código BIC, ISO 6346) y el vehículo (VIN, matrícula) son contexto de las unidades, como enlace o como custodio (§1.1, §5.2). Este registro no es un registro de objetos ni de activos: GS1 dice qué es una cosa y en qué envío va; este registro dice cuál es físicamente y si sigue siendo la misma.

1. Cómo se hace única una unidad (las tres vías)

1.1 Qué es una unidad (perfiles)

Una unidad es una cosa física que viaja como una sola pieza bajo una custodia (en castellano, un bulto). Cada unidad se registra con un perfil (unit_profile) que fija qué rasgos de la firma física se esperan y dónde se capturan. Perfiles de esta versión:

Perfil Qué es Alcance en v0.1
parcel caja, sobre, bidón o bulto cerrado que viaja como una pieza núcleo: perfil completo, niveles 0–3, captura de estación
pallet_load un palé cargado: la carga estibada y enfardada, no el palé de madera. El SSCC etiqueta el envío, pero nada ata esa etiqueta a esa carga física; la consolidación N→1 (§5.3) la modela tal cual núcleo: niveles 0–2; rasgos propios en v0.2
container_load la carga estibada dentro de un contenedor. El contenedor (código BIC) y el precinto (ISO 17712) son enlaces (§5.2), no la unidad niveles 0–2; rasgos propios en v0.2
tote cubeta, jaula, roll, barril, bombona: envases reutilizables, con o sin GRAI niveles 0–2; rasgos propios en v0.2
bag saca postal, bolsa de valija, equipaje (la etiqueta IATA bag_tag es por viaje, no por maleta) niveles 0–2; rasgos propios en v0.2
crate caja de transporte de arte, instrumentos o maquinaria niveles 0–2; rasgos propios en v0.2

Lo que no es una unidad: el contenedor marítimo y el vehículo. Tienen emisor permanente y serie grabada (BIC con dígito de control; VIN y matrícula con registros estatales), y la matrícula identifica a una persona. Son el siguiente nivel de la misma jerarquía: el contenedor es a la carga lo que el palé es al paquete, y el registro identifica la carga en cada nivel, no el recipiente permanente. El vehículo entra solo como custodio (holder_class: vehicle, §5.2) con referencia opaca, nunca la matrícula en claro.

1.2 Las tres vías

Una persona tiene un documento emitido por un tercero y una biometría. Una unidad no trae documento: se lo da el registro en el alta, y se ata al objeto por tres vías a la vez. (La tabla describe el perfil parcel; en los demás perfiles cambian los rasgos de la firma, no las vías.) Ninguna sola basta; las tres juntas son más de lo que tiene el KYC de personas, porque la historia sellada no existe allí.

Vía Qué es Quién la garantiza Qué pasa si falla
Documento el PARCID impreso o pegado al paquete (QR, texto en la etiqueta) el registro: aleatorio, con control, sin colisiones se pierde o se copia → entran las otras dos
Biometría la firma física (§6): geometría, peso, forma 3D, textura del cartón en parches fijos, cinta, disposición de la etiqueta, aspecto, daños el acta de alta sellada con firma del dispositivo y sello de tiempo (“certificado de nacimiento”) etiqueta copiada en otra caja → la firma no coincide
Historia la cadena sellada de avistamientos y el linaje (abrir, consolidar, reembalar = nuevo ID enlazado) cada acta de avistamiento, encadenada dos cajas idénticas no pueden tener la misma historia

1.3 Qué hay que subir para crear un ID (perfil parcel)

El registro acepta desde casi nada hasta una captura de estación. Cuanto más se sube, más alto es el nivel de garantía (§7) y más capaz es la comparación posterior.

Nivel Qué se sube Qué distingue
Mínimo (L0) nada obligatorio salvo la clase de captura. Opcional: medidas, peso solo el documento: el ID. Sirve para enlazar referencias, no para reconocer el paquete
Básico (L1) medidas (mm) y peso (g) con tolerancias, 2 fotos (cenital y una perspectiva con las 6 caras implícitas) o sus hashes, firma Ed25519 del cliente separa el paquete de la mayoría de los que viajan el mismo día por la misma ruta (geometría + peso + aspecto)
Recomendado (L2) lo anterior + parches de textura del cartón en 4 posiciones fijas respecto a la esquina de origen (§6.4), trazado de la cinta, disposición de la etiqueta (sin su contenido), atestación del dispositivo, nonce de frescura separa dos cajas idénticas de fábrica recién cerradas: la textura de fibras y manchas y el trazado de la cinta son distintos en cada caja
Estación (L3) lo anterior capturado en geometría normalizada (origen 0·0·0, reglas, cámaras fijas, luz propia) + nube de puntos + báscula calibrada + coherencia multisensor comparable entre máquinas y a lo largo del tiempo; es la “cabina de fotos de pasaporte” del paquete

Lo que no hace único a un paquete y no se debe usar como identidad: el número de seguimiento (lo pone el transportista, se reutiliza y se copia), el SSCC (identifica la unidad logística de un envío, no el objeto físico ni su estado; GS1 permite reutilizarlo pasado un plazo, lleva el prefijo de empresa del emisor y es enumerable), el contenido de la etiqueta (datos personales; además se reimprime). Estos tres se enlazan al PARCID (§5.2), no lo sustituyen.

El registro no compite con el SSCC: es una capa sobre él. Si la unidad trae SSCC, el registro lo acepta como clave de entrada de primera clase: se da de alta con links[kind=sscc] y se resuelve por /00/{SSCC} (§3.3), y el registro le añade lo que el SSCC no tiene, la firma física y la historia sellada. El PID queda por debajo como clave perpetua, porque el SSCC se reutiliza, identifica al emisor y se puede enumerar. Si la unidad no trae nada, el registro le da un PID gratis.

2. El identificador

2.1 Forma canónica

PID-1-7QC3-Z79D-G6RZ-8E
│   │ └──────┬──────┘ └┬┘
│   │        │         └ 2 símbolos de control (10 bits)
│   │        └ 12 símbolos de carga aleatoria (60 bits) en 3 grupos de 4
│   └ versión del esquema (1 símbolo)
└ prefijo fijo
  • Alfabeto: Base32 Crockford, 0123456789ABCDEFGHJKMNPQRSTVWXYZ (sin I, L, O, U). Al leer se aceptan minúsculas, I/L→1, O→0, y se ignoran guiones y espacios. La forma canónica es mayúsculas con guiones.
  • Longitud: 23 caracteres con guiones; 18 sin ellos (PID + 15 símbolos). Cabe en una línea de etiqueta, se dicta por teléfono y se teclea sin ambigüedad.
  • Carga: 60 bits aleatorios del generador criptográfico del registro (o del bloque reservado, §2.4). Ninguna información dentro: ni fecha, ni emisor, ni secuencia. Espacio: 2^60 ≈ 1,15 × 10^18.
  • Versión: un símbolo. 1 es esta especificación. Permite alargar la carga o cambiar el control sin romper lo emitido.

2.2 Por qué 60 bits y no más

  • La unicidad no la da la aleatoriedad, la da el registro: el ID se emite centralmente (o desde un bloque reservado) y la base rechaza duplicados. No se necesita la propiedad “sin colisiones por cumpleaños”; se necesita que el espacio sea disperso para que nadie enumere la base ni adivine IDs.
  • Dispersión: con 10^11 IDs registrados (un año del comercio mundial de paquetes), un ID al azar acierta uno existente con probabilidad 10^11 / 1,15 × 10^18 ≈ 9 × 10^-8. Con límites de consulta (§10) la enumeración es impracticable. Con 10^14 IDs (siglos de uso) la densidad sube a 10^-4; antes de llegar ahí se emite la versión 2 con carga de 80 bits.
  • 60 bits son 12 símbolos: tres grupos de cuatro, legibles y dictables. El borrador del documento 04 proponía 70 bits (KYP-1-7XBD-04QM-3F7K-9H, 14 símbolos): con la misma longitud, esta especificación dedica 12 símbolos a la carga y los 2 últimos al control. Mantener 70 bits de carga obligaría a alargar el ID a 16 símbolos para conservar el control. DECISIÓN 1 (decidida): 60 bits + 2 símbolos de control con 23 caracteres.

2.3 Control (sistema puro ISO 7064 “MOD 1021-32”)

  • Entrada: los 13 símbolos versión + carga, como valores 0–31. P = Σ v_i · 32^(12−i) mod 1021.
  • Control: C = (1 − 1024 · P) mod 1021 (y como 1024 ≡ 3, C = (1 − 3P) mod 1021), codificado en dos símbolos: C div 32, C mod 32. Siempre C ≤ 1020 < 1024, así que cabe.
  • Verificación: la misma suma sobre los 15 símbolos (versión + carga + control) debe ser ≡ 1 (mod 1021).
  • Propiedades (1021 es primo, 32 es invertible módulo 1021): detecta todas las sustituciones de un símbolo, todas las transposiciones de símbolos adyacentes, y el 99,9 % de los errores aleatorios dobles (1 de cada 1021 pasa). Comprobado con ref/parcid.py: 0 de 139.500 sustituciones y 0 de 4.068 transposiciones no detectadas; 30 de 20.000 errores dobles aleatorios no detectados (0,15 %).
  • Implementación de referencia: ref/parcid.py (generar, normalizar, validar; sin dependencias). Ejemplos válidos: PID-1-B92B-YPQ0-8303-JW, PID-1-91QW-5PA4-R8CT-KZ, PID-1-5GP2-J3D9-G4WF-RE, PID-1-Y51Z-HM66-Q1YQ-FE, PID-1-7QC3-Z79D-G6RZ-8E, PID-1-7G61-S4XF-X5P2-HA.

2.4 Emisión

  • En línea: el registro genera la carga, comprueba que no existe y la inserta en una transacción; si colisiona (probabilidad ≈ n/2^60), repite.
  • Fuera de línea (estaciones y SDK sin red): el dispositivo pide por adelantado un bloque reservado de hasta 1.000 IDs con POST /reservations, firmando con su clave Ed25519 la petición { schema:"parcid.reservation-request/1", public_key, count, requested_at, nonce } (a menos de 5 minutos de la hora del registro). Solo para dispositivos homologados o con clave de desarrollador, con límite diario por dispositivo. El alta con un ID reservado ("pid" en POST /parcels) exige la firma de ese mismo dispositivo. Los IDs no usados en 90 días se queman: nunca se emiten a otro (DECISIÓN 8, decidida). La reserva es un acta (§7.5).
  • Un ID nunca se reutiliza, ni siquiera revocado.

3. Resolución pública

3.1 URL

  • Forma canónica: https://{host}/{PID}, p. ej. https://parcid.com/PID-1-7QC3-Z79D-G6RZ-8E. El host del registro es parcid.com (DECISIÓN 10, decidida el 23/10/2026). Si el host cambiara alguna vez, el antiguo redirigiría con 301 para siempre.
  • Variantes aceptadas (redirigen 301 a la canónica): minúsculas, sin guiones, con I/L/O.
  • HTML para navegadores; JSON si Accept: application/json o con sufijo .json. Igual contenido.
  • Metadatos del registro: https://{host}/.well-known/parcid (versión de la especificación, claves públicas del registro con kid, URL de la exportación, política de límites).

3.2 QR

  • Contenido del QR: la URL canónica completa (no solo el ID), para que cualquier lector lo abra. Corrección de errores M. Junto al QR, el ID en texto en una línea.
  • Si el QR se imprime en la estación, va en una etiqueta aparte o dentro de la etiqueta de envío existente; nunca tapa los códigos del transportista.
  • El PARCID no es una clave GS1 y no se pretende que lo sea. El estándar GS1 Digital Link admite pares clave-valor no GS1 en la cadena de consulta, de modo que un enlace de un SSCC puede llevar el ID: https://{host}/00/{SSCC18}?pid=PID-1-....
  • El SSCC es clave de entrada de primera clase. POST /parcels con links[kind=sscc] registra la unidad bajo su SSCC desde el alta (el registro verifica el dígito de control GS1), y el resolutor acepta GET /00/{SSCC18} y devuelve los PIDs enlazados a ese SSCC por su titular (§5.2), si existen y si el titular los hizo públicos. Como GS1 permite reutilizar un SSCC pasado un plazo, la respuesta lista todos los PID enlazados con su fecha de enlace, el activo primero; el PID es la clave perpetua, el SSCC la puerta de entrada.
  • Quién respalda el enlace. Cualquier titular puede enlazar públicamente un SSCC a su unidad, así que GET /00/{SSCC} devuelve como mucho 20 PIDs y cada uno con claim: operator (unidad dada de alta por un operador identificado con clave de desarrollador) o self_declared (sin verificar); primero los de operador.
  • A la inversa, la resolución de un ID lista los SSCC y seguimientos enlazados solo si el titular los publicó; por defecto son privados.

3.4 Respuesta de resolución (lo público)

Lo que cualquiera ve: existe, cuándo se registró, con qué perfil de unidad, clase de captura y nivel de garantía, en qué estado está, si tiene linaje, el último avistamiento verificado (fecha y clase, no lugar exacto), y las huellas que permiten verificar el acta de alta. Nunca: imágenes, medidas, peso, referencias privadas, quién lo registró, direcciones.

{
  "pid": "PID-1-7QC3-Z79D-G6RZ-8E",
  "spec": "parcid/1",
  "status": "active",
  "unit_profile": "parcel",
  "registered_at": "2026-11-18T09:41:07Z",
  "capture_class": "station",
  "assurance_level": 3,
  "lineage": { "parents": 0, "children": 0 },
  "last_sighting": { "at": "2026-11-21T16:02:33Z", "capture_class": "station", "assurance_level": 3, "result": "match" },
  "sightings_count": 2,
  "enrolment": {
    "record_hash": "sha256:4c3f1a…",
    "registry_signature_kid": "reg-2026-1",
    "timestamp": { "rfc3161": true, "tsa": "tsa.example", "gen_time": "2026-11-18T09:41:09Z" },
    "anchor": { "day": "2026-11-18", "merkle_root": "sha256:a91e…", "url": "https://parcid.com/anchors/2026-11-18" }
  },
  "public_links": [ { "kind": "sscc", "value": "003412345678901238" } ],
  "resolved_at": "2026-11-22T10:00:00Z"
}

3.5 Códigos

HTTP Significado
200 existe (cualquier estado, incluido revoked)
301 forma no canónica → canónica
404 formato válido, no registrado ({"error":"not_found","valid_format":true})
422 formato o control inválidos ({"error":"invalid_id","reason":"check"})
429 límite de consultas (§10)

4. API libre (v1)

Base: https://{host}/api/v1. JSON UTF-8. Sin cuenta para registrar y resolver. Toda llamada queda en el registro de API del propio registro durante 90 días, sin IP y sin llaves (se enmascaran); los cuerpos de respuesta solo se guardan en errores y operaciones que cambian algo.

Método y ruta Auth Qué hace
GET /verify/{PID} ninguna valida formato y control, normaliza, dice si existe y su estado. No cuenta como resolución completa
GET /parcels/{PID} ninguna resolución pública (§3.4)
GET /parcels/{PID} llave del paquete resolución completa: acta de alta, firma física, evidencias (URLs firmadas de corta vida), enlaces privados, avistamientos con detalle
POST /parcels ninguna (o clave de desarrollador para subir límites) alta: crea el ID, devuelve la llave del paquete una sola vez
POST /parcels/{PID}/links llave añade referencias externas (seguimiento, SSCC, pedido…) con visibilidad
POST /parcels/{PID}/lineage llave de los padres declara apertura, consolidación, división o reembalaje y crea el hijo
POST /parcels/{PID}/status llave retired (entregado y cerrado) o lost
POST /parcels/{PID}/rotate-key llave emite una llave nueva y anula la anterior
POST /parcels/{PID}/sightings llave + servicio de pago del operador avistamiento con comparación (§8)
GET /parcels/{PID}/proof ninguna prueba de inclusión de cada acta de la unidad en su árbol diario, con la declaración firmada y las pruebas OpenTimestamps (§7.3)
POST /reservations firma del dispositivo + dispositivo homologado o clave de desarrollador bloque de hasta 1.000 IDs para un dispositivo sin red (§2.4)
GET /anchors · GET /anchors/{día} · GET /anchors/{día}.ots ninguna cadena de declaraciones diarias, cada una con su firma y sus pruebas .ots (§7.3)
GET /export · GET /export/{fichero} ninguna índice de exportaciones diarias y descarga (JSON Lines + gzip, CC0, §12)
GET /stats ninguna cifras agregadas del registro: unidades por perfil, nivel y mes; actas por tipo; última ancla y exportación; volumen y latencia de 24 h (§10.1)
GET /health ninguna (detalle con el token del operador) salud para monitores: estado y comprobaciones básicas; con token, también almacén, disco, atraso de anclas y de tareas
GET /metrics token del operador exposición Prometheus (§10.1)
POST /beacon ninguna contador de páginas de la web del registro, sin cookies ni IP (§10.1)
GET /.well-known/parcid · GET /00/{SSCC} · GET /{PID} ninguna metadatos y claves · GS1 Digital Link · resolución corta (§3)

4.1 La llave del paquete (owner key)

  • Formato: pidk_ + 52 símbolos Crockford (260 bits). Se devuelve una sola vez en la respuesta del alta. El registro guarda solo SHA-256(llave).
  • Da control total sobre ese paquete y nada más: leerlo entero, enlazar, declarar linaje, cambiar estado, rotar. No identifica a nadie: no hay cuenta detrás obligatoria. Quien la pierde no puede recuperarla (no hay datos personales a los que atar una recuperación); por eso se recomienda guardarla en el sistema del cliente junto al ID, o usar una cuenta de desarrollador (opcional) que custodie las llaves de sus paquetes.
  • Se envía en Authorization: Bearer pidk_…. Nunca en la URL.
  • Linaje: el hijo nace con llave propia; se devuelve a quien declaró la operación.

4.2 Alta: POST /parcels

Cuerpo multipart/form-data (si se suben archivos) o application/json (si solo se envían hashes y la firma física ya calculada). Campos:

Campo Obl. Contenido
capture_class sí station · mobile_sdk · third_party_device · api
unit_profile no parcel (por defecto) · pallet_load · container_load · tote · bag · crate (§1.1). En v0.1 solo parcel alcanza el nivel 3
captured_at sí ISO 8601 UTC del dispositivo
dimensions_mm no { "l":..,"w":..,"h":.., "tolerance_mm":.. }
weight_g no `{ “value”:.., “tolerance_g”:.., “scale_class”:“calibrated
evidence[] no archivos (JPEG/PNG/WebP ≤ 25 MB, MP4 ≤ 200 MB, PLY/PCD ≤ 50 MB) o { "role":"top","sha256":"…","mime":"image/jpeg","bytes":… } si el cliente los conserva. Roles: top, perspective, side_*, label_layout, pointcloud, video
signature no firma física ya calculada por el cliente según §6 (la estación y el SDK la calculan; un cliente simple la deja al registro)
device no { "kid":"…", "public_key":"ed25519:base64", "attestation": {...}, "sequence": 1234, "nonce":"…" }. Si se envía, device_signature y sequence son obligatorios: un dispositivo sin firma no cuenta (nadie puede usar la clave pública ajena de una estación para gastarle el cupo, adelantarle la secuencia o hacer que un acta parezca suya)
device_signature con device Ed25519 sobre capture_hash (§7.2)
links[] no referencias externas con kind, value, visibility
evidence_retention no hashes_only (por defecto en el nivel gratuito tras 30 días) · store (resguardo de pago del operador)
consent sí { "no_personal_data": true, "terms_version": "2026-10" }: quien registra declara que no sube datos personales fuera de las imágenes y acepta que las imágenes se guardan cifradas

Respuesta 201:

{
  "pid": "PID-1-7QC3-Z79D-G6RZ-8E",
  "owner_key": "pidk_3K9FZX1Q7MWB8Y2RHA0C5TDVNEG4PJ6S9ZX1Q7MWB8Y2RHA0C5TDVN",
  "owner_key_notice": "Guárdala ahora. No se vuelve a mostrar y no se puede recuperar.",
  "assurance_level": 3,
  "resolution_url": "https://parcid.com/PID-1-7QC3-Z79D-G6RZ-8E",
  "qr_url": "https://parcid.com/PID-1-7QC3-Z79D-G6RZ-8E/qr.svg",
  "enrolment_record": { "...": "acta completa, §7.3" },
  "free_tier": { "evidence_kept_until": "2026-12-18T09:41:07Z", "hashes_kept": "forever" }
}

4.3 Verificación: GET /verify/{PID}

{ "input": "pid-1-7qc3-z79d-g6rz-8e", "canonical": "PID-1-7QC3-Z79D-G6RZ-8E", "format": "valid", "check": "valid", "registered": true, "status": "active", "resolution_url": "https://parcid.com/PID-1-7QC3-Z79D-G6RZ-8E" }

Con control erróneo: { "input":"PID-1-7QC3-Z79D-G6RZ-8F", "format":"valid", "check":"invalid", "registered":null } y HTTP 422. Este endpoint es para lectores de etiqueta, formularios y agentes: barato, sin límite estricto y sin contar como resolución.

4.4 Errores

{"error":"code","message":"texto","request_id":"…"} con códigos: invalid_id, invalid_request, not_found, method_not_allowed, unauthorized (llave incorrecta), forbidden (estado no lo permite), payload_too_large, unsupported_media, rate_limited, consent_required, lineage_conflict, paid_service (servicio del operador no contratado), unavailable, server_error.

5. Estructura del registro

5.1 Núcleo inmutable (no cambia nunca)

pid, spec_version, unit_profile, registered_at, capture_class, assurance_level al alta, dimensions_mm y weight_g (inmutables pero privados: solo con llave), signature_hash (SHA-256 de la firma física canónica), evidence[] (rol, mime, bytes, sha256), device.kid, device_signature, record_hash, registry_signature, tsa_token, anchor_day. Es exactamente el contenido del acta de alta (§7.3).

5.2 Enlaces mutables (con historia; nada se borra, se añade)

  • Referencias externas: kind ∈ tracking, sscc (clave de entrada de primera clase, §1 y §3.3; dígito de control GS1 verificado), sgtin, grai (envase reutilizable GS1), bic (contenedor, ISO 6346: 4 letras + 6 dígitos + dígito de control, que el registro verifica), seal (precinto ISO 17712), bag_tag (etiqueta de equipaje IATA, 10 dígitos), order, invoice, customs_declaration, custom; value; visibility ∈ private (por defecto), public; added_at, removed_at. Contenedores, precintos y envases son contexto de la unidad (§0.6), nunca unidades.
  • Custodia: holder_class ∈ sender, warehouse, forwarder, carrier, vehicle, customs, recipient, other; holder_ref opaco elegido por el titular (nunca un nombre personal; para vehicle, nunca la matrícula ni el VIN en claro); from, to.
  • Avistamientos: §8.
  • Estado: active → retired (cerrado por el titular) · superseded (consumido por linaje) · lost (declarado por el titular) · revoked (por el registro, §11). Cada cambio con fecha y actor (owner, registry).

5.3 Linaje

Un paquete es un objeto en un estado. Si se abre, consolida, divide o reembala, nace un ID nuevo enlazado:

Operación Padres → hijos Estado de los padres
open 1 → 1 (misma caja abierta y vuelta a cerrar) superseded
repack 1 → 1 (otra caja) superseded
consolidate N → 1 superseded todos
split 1 → N superseded
attach 1 → 1 (el mismo objeto gana una captura de nivel superior, p. ej. móvil → estación) sigue active; es un avistamiento, no linaje

Cada operación es un acta (§7) firmada por quien la hace, con los pid de padres e hijos y las huellas de las evidencias. La resolución de un hijo muestra sus padres y la de un padre sus hijos; la historia completa se sigue por el grafo.

6. La firma física

6.1 Formato

Objeto JSON canónico, compacto (objetivo < 300 KB), separado de las evidencias en bruto. Se guarda íntegro en el registro (no contiene datos personales) y se publica su hash en el acta; su contenido lo lee quien tiene la llave y los servicios de comparación.

{
  "schema": "parcid.signature/1",
  "unit_profile": "parcel",
  "capture_class": "station",
  "frame": { "origin": "corner_0_0_0", "axes": "l_w_h", "unit": "mm", "calibration": "pns-ruler-60x60/1" },
  "features": [
    { "type": "geometry", "model": "geom/1", "value": { "l": 412, "w": 306, "h": 218, "volume_cm3": 27493, "ratios": [1.346, 1.404] },
      "tolerance": { "mm": 3 }, "quality": 0.98, "stability": "high" },
    { "type": "weight", "model": "weight/1", "value": { "g": 3840, "tolerance_g": 5, "scale_class": "calibrated" }, "quality": 1.0, "stability": "high" },
    { "type": "shape3d", "model": "pcd-desc/1", "dim": 256, "dtype": "float16", "vector": "base64…", "quality": 0.91, "stability": "medium" },
    { "type": "texture_patch", "model": "paper-fp/1", "patch": "A", "face": "top", "pos_mm": [30, 30], "size_mm": 25, "dim": 512, "dtype": "uint8", "vector": "base64…", "quality": 0.87, "stability": "high" },
    { "type": "texture_patch", "model": "paper-fp/1", "patch": "B", "face": "top", "pos_mm": [382, 30], "size_mm": 25, "dim": 512, "dtype": "uint8", "vector": "base64…", "quality": 0.84, "stability": "high" },
    { "type": "texture_patch", "model": "paper-fp/1", "patch": "C", "face": "front", "pos_mm": [30, 30], "size_mm": 25, "dim": 512, "dtype": "uint8", "vector": "base64…", "quality": 0.80, "stability": "high" },
    { "type": "texture_patch", "model": "paper-fp/1", "patch": "D", "face": "right", "pos_mm": [30, 30], "size_mm": 25, "dim": 512, "dtype": "uint8", "vector": "base64…", "quality": 0.78, "stability": "high" },
    { "type": "tape", "model": "tape-path/1", "value": { "faces": ["top", "bottom"], "polyline_mm": [[0, 153], [412, 153]], "width_mm": 48, "overlaps": 1, "color": "brown" }, "quality": 0.9, "stability": "high_until_opened" },
    { "type": "label_layout", "model": "label-geo/1", "value": { "face": "top", "bbox_mm": [110, 40, 210, 148], "rotation_deg": 0.8, "barcodes": 2, "print_defects": 3 }, "quality": 0.93, "stability": "high_until_relabel", "note": "solo geometría, nunca el contenido" },
    { "type": "appearance", "model": "box-emb/1", "views": ["top", "perspective"], "dim": 512, "dtype": "float16", "vector": "base64…", "quality": 0.9, "stability": "medium" },
    { "type": "damage_map", "model": "damage/1", "value": { "items": [ { "face": "front", "bbox_mm": [300, 150, 360, 210], "kind": "dent", "severity": 0.3 } ] }, "quality": 0.85, "stability": "changes_informatively" }
  ],
  "hash": "sha256:9b2d…"
}

6.2 Reglas

  • model versiona el algoritmo que produjo el rasgo; dos rasgos solo se comparan si comparten model o hay un adaptador declarado. Nunca se recalcula una firma antigua: se añade una nueva captura (avistamiento) con los modelos nuevos.
  • quality ∈ [0,1] es la calidad de la captura del rasgo (enfoque, oclusión, cobertura). stability describe cómo envejece el rasgo.
  • Fusión ponderada: la comparación (§8) puntúa por rasgo y luego globalmente; el peso de cada rasgo depende de su quality en ambas capturas, de su stability y del perfil de umbrales calibrado para el par de clases de captura.
  • Cero datos personales: label_layout guarda caja, rotación, número de códigos y defectos de impresión. Está prohibido incluir texto OCR, códigos de barras decodificados o recortes de etiqueta en la firma. El OCR, si se hace, vive en la cuenta del cliente del operador, no en el registro.

6.3 Rasgos por clase de captura (perfil parcel)

Rasgo station mobile_sdk third_party_device api
geometry medida (nube de puntos) estimada (reglas/ARKit/ARCore) según dispositivo declarada
weight báscula calibrada no (o declarado) según dispositivo declarada
shape3d sí parcial según dispositivo no
texture_patch 4 parches, 2 cámaras 2–4 parches, menor resolución según dispositivo no
tape, label_layout, appearance, damage_map sí sí según dispositivo no

6.4 Posiciones fijas de los parches de textura

Los parches se toman en coordenadas fijas respecto a la esquina de origen (0·0·0: la que apoya en el ángulo de las reglas en la estación; en móvil, la esquina inferior izquierda de la cara superior vista desde la etiqueta). Parche de 25 × 25 mm: A cara superior a (30, 30) mm; B cara superior a (L − 30, 30); C cara frontal a (30, 30); D cara derecha a (30, 30). Si una posición cae sobre etiqueta o cinta, se desplaza al punto libre más cercano y se anota pos_mm real. Así la misma caja produce parches comparables en capturas distintas y la textura separa cajas idénticas de fábrica.

6.5 Perfiles de unidad

La firma física lleva unit_profile junto a capture_class. El perfil parcel es el definido en §6.1–6.4. Para pallet_load, container_load, tote, bag y crate, la v0.2 admite los rasgos genéricos (geometry, weight, appearance, damage_map, label_layout) y deja para la v0.3 los rasgos propios y las posiciones fijas de los parches: film de enfardado y patrón de estiba en el palé cargado; veta, clavos y sellos de bloque en envases de madera; cremalleras, asas y textura de tela en el equipaje. Hasta entonces esos perfiles no pasan del nivel 2 (DECISIÓN 9).

7. Actas: formato, hash, firmas, sello

7.1 Canonicalización y hash

  • JSON canónico según RFC 8785 (JCS): claves ordenadas, sin espacios, números en formato ES6, UTF-8. Los binarios van en base64; las huellas como sha256: + hex minúsculas. Restricción de números para evitar ambigüedades entre implementaciones: solo enteros hasta 2^53 o decimales menores de 10^9 con un máximo de 6 decimales; cualquier otro número se rechaza.
  • record_hash = SHA-256(bytes canónicos del acta sin los campos "signatures" ni "timestamp").

7.2 Firmas

  • Del dispositivo o cliente (opcional, sube el nivel): Ed25519 sobre capture_hash = SHA-256 del subobjeto canónico que el dispositivo controla (pid, captured_at, capture_class, device, measurements, signature_hash, evidence, liveness). El dispositivo no puede firmar el acta completa porque no conoce received_at ni el nivel asignado; como el acta incluye capture_hash, la firma del registro sobre record_hash cubre también lo firmado por el dispositivo. La firma se calcula sobre los 32 bytes de la huella. kid = sha256: de la clave pública, 16 primeros bytes en hex. Las claves de estación se registran en el alta del dispositivo con el operador; las de SDK, por instalación (atestación Play Integrity / App Attest / WebAuthn cuando existe).
  • Del registro (siempre): Ed25519 con la clave publicada en /.well-known/parcid (kid tipo reg-2026-1), sobre record_hash. Prueba que el registro aceptó el acta tal cual.
  • Secuencia y cadena: device.sequence monotónica por clave; prev_record_hash encadena las actas de un mismo dispositivo. Rechazo de secuencias repetidas o retrocesos (cuarentena).

7.3 Libro de actas, ancla diaria y Bitcoin (DECISIÓN 3)

  • Libro de actas (solo se añade). Toda acta sellada —alta, linaje, avistamiento, cambio de estado, rotación de llave, reserva— entra en el libro con un número de orden global seq y el día del libro en que se selló. El día es el día UTC del sellado, salvo que ese día ya esté cerrado, en cuyo caso es el primer día abierto: ningún acta puede caer en un día ya anclado (el sellado toma un cerrojo compartido del libro y el cierre de un día, uno exclusivo).
  • Árbol del día (RFC 6962, SHA-256). Las hojas son los 32 bytes de cada record_hash del día en orden de seq. hoja = SHA-256(0x00 ‖ record_hash), nodo = SHA-256(0x01 ‖ izquierda ‖ derecha), árbol vacío = SHA-256(""). La prueba de inclusión se verifica con el algoritmo de RFC 9162 §2.1.3.2.
  • Declaración diaria encadenada (parcid.anchor/1), JSON canónico RFC 8785: { schema, registry, day, tree:"rfc6962-sha256", leaf:"record_hash", leaf_count, first_seq, last_seq, merkle_root, prev_statement_hash, registry_kid }. statement_hash = SHA-256(canónico); el registro firma esos 32 bytes con Ed25519. Cada declaración lleva la huella de la del día anterior: los días sin actas también se anclan (árbol vacío) y un hueco en la cadena es, por sí solo, una alarma.
  • Bitcoin con OpenTimestamps. El statement_hash de cada día se envía a varios calendarios públicos (por defecto a.pool.opentimestamps.org, b.pool.opentimestamps.org, a.pool.eternitywall.com) y se guarda una prueba .ots estándar por calendario; cuando el calendario la confirma en un bloque, la prueba se completa con la rama hasta la cabecera de Bitcoin. Gratis, sin tokens, sin carteras. Cualquiera verifica con el cliente oficial (ots verify) o con tools/verify: el mensaje en la atestación es la raíz Merkle del bloque.
  • Confirmado en Bitcoin significa que el registro comprobó, en un explorador de bloques configurado, que la raíz Merkle del bloque es el mensaje de la atestación; la palabra del calendario no basta. Las URIs de calendario que se siguen están en una lista cerrada, sin redirecciones y con tope de tamaño.
  • Prueba por unidad. GET /parcels/{PID}/proof devuelve, por cada acta de la unidad: record_hash, día, leaf_index, tree_size, audit_path, merkle_root, la declaración con su firma y sus pruebas .ots. Con eso y la clave pública de /.well-known/parcid se verifica todo sin consultar la base del registro.
  • RFC 3161 (opcional): timestamp.rfc3161.token = TimeStampToken DER en base64 sobre record_hash, con tsa y gen_time, de una TSA cualificada eIDAS para actas de operador que necesiten valor probatorio en la UE. No cambia el nivel de garantía: el tiempo lo fija el ancla diaria.
  • Nada de NFT ni de tokens con dueño. En cadena solo van huellas; la llave del paquete queda fuera de cadena. DECISIÓN 12 (opcional, pendiente): segunda capa en una L2 de Ethereum publicando por lotes el par PID + record_hash, para consulta en cadena sin descargar la exportación.

7.4 Ejemplo de acta de alta (enrolment)

{
  "schema": "parcid.record/1",
  "type": "enrolment",
  "pid": "PID-1-7QC3-Z79D-G6RZ-8E",
  "captured_at": "2026-11-18T09:40:58Z",
  "received_at": "2026-11-18T09:41:07Z",
  "capture_class": "station",
  "assurance_level": 3,
  "device": { "kid": "sha256:7f3a9c1e2b4d6f8091a2b3c4d5e6f708", "class": "station", "model": "packnsign-60", "sequence": 18233, "prev_record_hash": "sha256:11d0…", "attestation": "operator_registered", "nonce": "b64:Q3Z…" },
  "measurements": { "dimensions_mm": { "l": 412, "w": 306, "h": 218, "tolerance_mm": 3 }, "weight_g": { "value": 3840, "tolerance_g": 5, "scale_class": "calibrated" } },
  "signature_hash": "sha256:9b2d…",
  "evidence": [
    { "role": "top", "mime": "image/jpeg", "bytes": 2418833, "sha256": "sha256:0c7e…" },
    { "role": "perspective", "mime": "image/jpeg", "bytes": 2601120, "sha256": "sha256:51aa…" },
    { "role": "pointcloud", "mime": "application/vnd.ply", "bytes": 8120044, "sha256": "sha256:e3f9…" }
  ],
  "liveness": { "level": "high", "signals": ["device_attested", "scene_physics_ok", "nonce_in_frame", "multisensor_coherent"] },
  "links": [ { "kind": "sscc", "value": "003412345678901238", "visibility": "public" } ],
  "consent": { "no_personal_data": true, "terms_version": "2026-10" },
  "record_hash": "sha256:4c3f1a…",
  "signatures": [
    { "by": "device", "alg": "Ed25519", "kid": "sha256:7f3a9c1e2b4d6f8091a2b3c4d5e6f708", "sig": "base64…" },
    { "by": "registry", "alg": "Ed25519", "kid": "reg-2026-1", "sig": "base64…" }
  ],
  "timestamp": { "rfc3161": { "tsa": "tsa.example", "gen_time": "2026-11-18T09:41:09Z", "token": "base64…" }, "anchor": { "day": "2026-11-18", "merkle_root": "sha256:a91e…", "proof_url": "https://parcid.com/api/v1/parcels/PID-1-7QC3-Z79D-G6RZ-8E/proof" } }
}

(Las firmas y tokens del ejemplo son ilustrativos; los hashes se abrevian.)

7.5 Actas de cambio

Todo cambio de una unidad es un acta firmada y entra en el libro, encadenada con el acta de alta (unit_record_hash):

  • status: { schema, type:"status", pid, spec, at, unit_record_hash, from, to, actor:"owner"|"registry", reason_code?, registry }. La emiten el titular (retired, lost), el linaje (superseded) y el registro (revoked, §11).
  • rotation: { …, type:"rotation", owner_key_commitment } al rotar la llave del paquete. owner_key_commitment = "sha256:" + hex(SHA-256("parcid-owner-key-v1|" + hex(SHA-256(llave)))): no revela nada de una llave de 260 bits y permite a la auditoría comprobar que la llave vigente es la de la última rotación sellada (o la del alta, que lleva el mismo campo).
  • reservation: { schema, type:"reservation", reservation_id, device_kid, count, pids_sha256, expires_at, request_hash, at, registry }. pids_sha256 es la huella de la lista de IDs reservados (uno por línea); la lista solo la recibe el dispositivo.

7.6 Lo que el acta afirma y lo que no

  • capture_class_verified: true solo si la clase de captura la respalda un dispositivo homologado de esa clase que firmó (o si es api, que es declarativa). Una captura station sin estación homologada queda como declarada.
  • device.attestation: operator_registered solo con firma válida de un dispositivo homologado; si no, unverified.
  • owner_key_commitment: ver §7.5.
  • timestamp no forma parte de la huella: lo que fija el tiempo es la prueba de /proof (día del libro, árbol y ancla).

8. Avistamientos (sightings)

Un avistamiento es una nueva captura del mismo paquete reclamando un ID, con el resultado de la comparación 1:1 contra la firma del alta (o la del último avistamiento aceptado) y una explicación. Es un acta (§7) de tipo sighting. La parte pública (fecha, clase, nivel, resultado) aparece en la resolución; el detalle solo con la llave.

{
  "schema": "parcid.record/1",
  "type": "sighting",
  "pid": "PID-1-7QC3-Z79D-G6RZ-8E",
  "captured_at": "2026-11-21T16:02:20Z",
  "capture_class": "station",
  "assurance_level": 3,
  "device": { "kid": "sha256:2a…", "class": "station", "model": "packnsign-60", "sequence": 902 },
  "compared_to": { "record_hash": "sha256:4c3f1a…", "signature_hash": "sha256:9b2d…" },
  "match": {
    "profile": "station-station/v1",
    "score": 0.94,
    "decision": "match",
    "thresholds": { "match": 0.85, "review": 0.65 },
    "per_feature": [
      { "type": "geometry", "score": 0.99, "note": "±2 mm" },
      { "type": "weight", "score": 0.98, "note": "−6 g" },
      { "type": "texture_patch", "patch": "A", "score": 0.93 },
      { "type": "texture_patch", "patch": "B", "score": 0.91 },
      { "type": "tape", "score": 0.97 },
      { "type": "damage_map", "score": 0.70, "note": "abolladura nueva en cara frontal, severidad 0,3" }
    ],
    "explanation": "Misma geometría y peso; la textura de los parches A y B coincide; la cinta es la misma; hay un daño nuevo en la cara frontal."
  },
  "liveness": { "level": "high", "signals": ["device_attested", "scene_physics_ok", "nonce_in_frame", "multisensor_coherent"] },
  "custody": { "holder_class": "warehouse", "holder_ref": "opaque:mde-vlc-01" },
  "record_hash": "sha256:77be…",
  "signatures": [ { "by": "device", "alg": "Ed25519", "kid": "sha256:2a…", "sig": "base64…" }, { "by": "registry", "alg": "Ed25519", "kid": "reg-2026-1", "sig": "base64…" } ],
  "timestamp": { "rfc3161": { "tsa": "tsa.example", "gen_time": "2026-11-21T16:02:35Z", "token": "base64…" } }
}
  • decision ∈ match, review, no_match. Un no_match también se registra (es información: alguien presentó este ID con otra caja).
  • Los avistamientos de terceros sin captura (eventos del transportista vía el servicio de seguimiento del operador) se registran como sighting con capture_class: "api", assurance_level: 0 y sin match; informan de la ruta, no de la identidad.

9. Niveles de garantía (assurance level)

Nivel Nombre Requisitos Cómo se muestra
0 unverified alta por API sin firma de dispositivo ni evidencias verificadas “Registrado (sin verificar)”
1 signed firma Ed25519 de la clave del cliente, secuencia válida, al menos 2 evidencias con hash, medidas o peso “Firmado”
2 attested lo anterior + atestación del dispositivo (SDK, cuando el registro verifique Play Integrity / App Attest) o dispositivo dado de alta en la lista de dispositivos de confianza del registro + nonce de frescura + firma física con parches de textura “Atestado”
3 station lo anterior en estación registrada con geometría normalizada, báscula calibrada, nube de puntos y coherencia multisensor (el tiempo lo fija el ancla diaria; el sello RFC 3161 es opcional) “Estación”

El nivel es el del alta; cada avistamiento lleva el suyo. Un paquete registrado en nivel 1 que pasa por una estación obtiene un avistamiento de nivel 3 (attach), y la resolución muestra “mejor nivel alcanzado: 3”.

10. Nivel gratuito, límites y abuso

Concepto Valor (DECISIÓN 4, decidida; se revisa con datos de uso)
Altas sin clave de desarrollador 100 / mes por dirección IP y por clave de dispositivo
Altas con clave de desarrollador gratuita (correo verificado) 1.000 / mes
Resolución pública 60 / min por IP; 600 / min con clave
GET /verify 600 / min por IP
Evidencias en bruto del nivel gratuito 30 días cifradas (XChaCha20-Poly1305 por trozos, clave por paquete envuelta con la clave maestra del registro); después solo hashes. El resguardo largo es servicio del operador
Firma física y acta se conservan indefinidamente
Tamaño por alta 25 MB por imagen, 200 MB vídeo, 50 MB nube de puntos; 300 MB en total
  • Las altas sin firma de dispositivo quedan en nivel 0 y cuentan contra límites más estrictos.
  • Reputación de origen: cada clave de dispositivo, clave de desarrollador e IP acumula una puntuación; muchas altas huérfanas (sin avistamientos ni enlaces en 90 días) o incoherentes bajan el nivel máximo que puede obtener esa fuente. Pública en agregado, no por fuente.
  • Las cuotas de pago del operador (mínimo 300 €/mes por cliente) se aplican a avistamientos con comparación, prueba de vida, comparación 1:1 y 1:N y resguardo, nunca a la alta ni a la resolución.

10.0 Abuso: lo que el registro impide

  • Disco: sin clave de desarrollador ni dispositivo homologado no se guarda vídeo ni nube de puntos en bruto (solo sus huellas), y hay un tope de 1 GB al mes por IP y 5 GB al día en total; por debajo de 10 GB libres no se guarda ningún bruto (507). IPv6 cuenta por /64.
  • Imágenes: se leen las dimensiones de la cabecera antes de decodificar; más de 40 megapíxeles, 413.
  • Libro: cada unidad admite como mucho 100 operaciones del titular y 5 rotaciones de llave al día; /proof se pagina de 100 en 100; el árbol diario se construye en flujo, con memoria constante.
  • Reservas: la misma petición firmada no se puede repetir; tope diario por dispositivo y por clave de desarrollador.
  • Avistamientos: solo los registra un operador con el servicio contratado (clave de desarrollador con avistamientos; la llave de la unidad va en X-Parcid-Owner-Key).
  • Contador de la web: solo del mismo origen, con tope de filas distintas por día.
  • IP real: la cabecera del proxy solo se cree si la conexión llega de un proxy de confianza.
  • Unidad revocada: no sirve sus evidencias; la revocación por contenido ilegal destruye el bruto en el acto.

10.1 Medición sin datos personales

  • Técnica: recuento por minuto, ruta, método y código HTTP, con suma, máximo e histograma acumulado de latencia (≤10, 50, 100, 250, 500, 1000, 5000 ms) y bytes servidos. Se conserva 14 días por minuto.
  • Negocio y web: eventos por día con dos dimensiones: altas por clase y perfil, niveles, resoluciones, avistamientos, reservas, cambios de estado, límites alcanzados, errores, tareas programadas, páginas vistas por ruta e idioma y dominio de origen. Las rutas que llevan un PID se cuentan juntas, nunca por ID.
  • Nunca se guarda ni se cuenta nada por persona: ni IP (los límites usan un HMAC con sal del registro en contadores que caducan a los 40 días; las unidades no guardan nada de quien las registró), ni cookies, ni huella del navegador. La web respeta Do Not Track y Global Privacy Control y no envía nada en ese caso.
  • Exposición pública agregada en GET /stats; exposición completa para el operador en GET /metrics (Prometheus, con token).

11. Revocación y gobernanza del dato

  • El registro solo revoca (revoked) por abuso demostrado (altas masivas basura, contenido ilegal en evidencias, suplantación de dispositivo). La revocación es pública, motivada por código (abuse, illegal_content, device_compromised) y conserva el acta: un ID revocado sigue resolviendo con su estado.
  • El titular puede pedir el borrado de las evidencias en bruto en cualquier momento (derecho sobre imágenes que pudieran contener datos personales). Las huellas y la firma física no se borran: no son datos personales y sostienen la verificabilidad de terceros.
  • No existe “corrección” de un acta: se añade otra que la enmienda y la antigua queda superseded_by.

12. Exportación, verificación independiente y permanencia

  • Exportación diaria completa (parcid.export/1, JSON Lines + gzip, CC0) en GET /export/{fichero}, con índice en GET /export. Líneas, en este orden: export_header (registro, día, licencia, claves públicas) · una unit por unidad (solo lo público: pid_tag, pid_salt, estado, perfil, clase, niveles, record_hash, firma del registro, día del ancla, linaje por pid_tag y enlaces públicos) · una record por entrada del libro (seq, día, record_hash, tipo, pid_tag de su unidad) · una anchor por día (declaración, firma y pruebas .ots) · export_manifest (recuentos, content_sha256 de todas las líneas anteriores, manifest_hash y firma Ed25519 del registro). Con un solo fichero se recalculan todas las raíces diarias, la cadena de declaraciones y todas las firmas (tools/verify/cli.mjs export).
  • Exportación frente a enumeración (DECISIÓN 7, decidida el 23/10/2026; formato corregido tras la revisión de seguridad). La exportación nunca lleva el PID en claro. Cada unidad lleva una sal aleatoria propia de 16 bytes y su etiqueta: pid_salt (hex) y pid_tag = "sha256:" + hex(SHA-256(bytes(pid_salt) ‖ PID canónico en ASCII)); el linaje se expresa con las pid_tag de padres e hijos. Quien tiene un PID encuentra su unidad calculando una SHA-256 por unidad (tools/verify findInExport). Con una huella sin sal, la lista de PIDs se recuperaría por fuerza bruta en cuanto el registro tuviera millones de unidades (2⁶⁰/N intentos por PID); con sal por unidad cada una cuesta 2⁵⁹ intentos de media y no hay atajo. El volumen se publica como cifra agregada (§10.1).
  • Con el acta de alta, la clave pública del registro y la prueba de inclusión en el ancla, cualquiera verifica sin consultar al registro: recalcular el record_hash sobre el JSON canónico, comprobar las firmas Ed25519, seguir la ruta Merkle hasta la raíz publicada y verificar la prueba OpenTimestamps contra la cabecera del bloque de Bitcoin (y el token RFC 3161 con la cadena de la TSA, si lo hay).
  • Permanencia más allá del operador (DECISIÓN 11, decidida el 22/10/2026). La exportación pública diaria (CC0) se publica además en almacenamiento permanente de pago único (Arweave); el código del resolutor, la API y la herramienta de verificación se archivan en Software Heritage; cada versión de esta especificación se deposita en Zenodo con DOI. Con la exportación, la clave pública del registro y las pruebas de Bitcoin, cualquiera reconstruye el registro público sin el operador.
  • Resguardo de la base: cada día, volcado lógico consistente, comprimido y cifrado por trozos (XChaCha20-Poly1305) con una clave de un solo uso sellada para la clave pública de resguardo (X25519). El servidor no tiene la clave privada: quien entre en el servidor no puede leer los resguardos. Rotación 7 diarios, 5 semanales, 12 mensuales, copia a un almacén externo y simulacro de restauración automatizado (registry/tests/restore-drill.mjs).
  • Evidencias en bruto: no forman parte de la exportación. El operador ofrece un resguardo perpetuo como servicio de pago a precio de coste, cifrado con la llave del paquete; sin ese servicio rige la caducidad del nivel gratuito (§10).

13. Gobernanza del espacio de nombres

  • El operador actual (Know Your Parcel, división de MDE HUB SL) se compromete públicamente a ceder el espacio de nombres PID, la especificación, el resolutor y la exportación a una entidad sin ánimo de lucro cuando se cumpla una de estas condiciones (DECISIÓN 5, decidida): tres operadores externos emitiendo altas, o un millón de IDs activos, o 24 meses desde el lanzamiento público, o el interruptor de continuidad de abajo.
  • Interruptor de continuidad (DECISIÓN 11): si el operador deja de publicar el ancla diaria durante 90 días seguidos, cualquier espejo puede continuar el registro a partir de la última exportación y de las claves rotadas publicadas, y esa situación cuenta como condición automática de cesión.
  • Mientras tanto: contabilidad separada del coste del registro gratuito, comité técnico abierto para cambios de la especificación (versiones semánticas; los cambios de formato del ID solo con nuevo símbolo de versión), publicación de las claves del registro con fecha de rotación, y código del resolutor, la API y el verificador en Apache-2.0 en repositorio público para que cualquiera levante un espejo (DECISIÓN 6); los modelos de comparación del operador quedan fuera: el formato de la firma es público, el motor es del operador.

14. Compatibilidad con la plataforma del operador

  • El registro tiene base de datos propia (migraciones en registry/sql/), separada de la del operador: así se puede ceder, espejar y auditar entero sin arrastrar datos del operador. El operador es un cliente más de la API. En su plataforma solo guarda el pid (y la llave del paquete, cifrada) junto a sus propios envíos y actas.
  • El servicio de seguimiento del operador aporta avistamientos de clase api, nivel 0.
  • El operador (Know Your Parcel, asociado a GS1/AECOC con prefijo de empresa propio) emite SSCC para las unidades que maneja y los imprime junto al PID en la misma etiqueta. El registro no emite SSCC nunca: GS1 prohíbe compartir prefijos de empresa; el registro solo los acepta como clave de entrada (§1, §3.3).
  • “Parcel Match” del operador pasa a ser la comparación 1:1 de §8, calibrada con el piloto (fase 4 de este hilo).

15. Decisiones (resumen)

Decididas por Damián Guerschuny (“todo bien, tira para delante”, 23/10/2026, sobre las propuestas de esta especificación):

  1. Bits: 60 bits de carga + 2 símbolos de control, 23 caracteres (§2.2).
  2. Host: parcid.com.
  3. Sellado: ancla diaria en Bitcoin con OpenTimestamps; RFC 3161 opcional (§7.3).
  4. Límites del nivel gratuito: los de la tabla de §10; se revisan con datos de uso reales.
  5. Condición de cesión: la de §13 (tres operadores externos, un millón de IDs activos, 24 meses desde el lanzamiento público o el interruptor de continuidad).
  6. Licencias: CC BY 4.0 (especificación), CC0 (referencia, verificador y datos), Apache-2.0 (resolutor y API).
  7. Exportación: sin PIDs en claro; cada unidad identificada por SHA-256(sal propia ‖ PID) (§12).
  8. Bloques reservados: POST /reservations para dispositivos homologados o con clave de desarrollador; los IDs caducados se queman (§2.4).
  9. Alcance: unidades en tránsito con los perfiles de §1.1; los distintos de parcel limitados al nivel 2 hasta v0.3 (§6.5).
  10. Nombre: PARCID; prefijo del identificador PID.
  11. Permanencia: Arweave, Software Heritage, Zenodo, resguardo perpetuo de evidencias a precio de coste, interruptor de 90 días (§12, §13).

Pendiente: 12. Opcional: segunda capa en una L2 de Ethereum con el par PID + record_hash por lotes (§7.3).

16. Historial

  • v0.1 · 22/10/2026 · primer borrador completo (identificador, resolución, API libre con llave del paquete, firma física, actas, avistamientos, niveles, límites, gobernanza).
  • v0.1 · 22/10/2026 · alcance ampliado a unidades en tránsito: principio 6, perfiles de unidad (§1.1, §6.5), unit_profile en acta, API y resolución, enlaces bic/seal/grai/bag_tag, custodio vehicle, OpenTimestamps en §7.3, decisiones 9 y 10.
  • v0.1 · 22/10/2026 · decisiones de Damián: el registro es una capa sobre el SSCC (clave de entrada de primera clase, §1, §3.3, §5.2, §14); DECISIÓN 3 cerrada con ancla en Bitcoin por OpenTimestamps (§7.3); DECISIÓN 6 cerrada con Apache-2.0 para resolutor, API y verificador; DECISIÓN 11 permanencia (Arweave, Software Heritage, Zenodo, resguardo perpetuo, interruptor de 90 días, §12, §13); DECISIÓN 12 nueva (L2 opcional).
  • v0.2 · 23/10/2026 · nombre PARCID y host parcid.com; libro de actas con día de libro y cerrojo de cierre, árbol RFC 6962, declaración diaria encadenada y firmada, OpenTimestamps por calendario y /proof (§7.3); actas de cambio status, rotation, reservation (§7.5); medición sin datos personales y rutas /stats, /health, /metrics, /beacon (§4, §10.1); formato de exportación parcid.export/1 con pid_sha256 (§12); resguardo cifrado para clave pública (§12); base de datos propia del registro (§14); decisiones 1, 2, 4, 5, 7, 8, 9 y 10 cerradas (§15).
  • v0.2 · 23/10/2026 · tras una revisión de seguridad independiente: exportación con sal por unidad (pid_tag, pid_salt) en lugar de sha256(PID); firma y secuencia obligatorias con device; capture_class_verified y owner_key_commitment en las actas (§7.6); claim en GS1 (§3.3); linaje público solo con recuentos; avistamientos solo de operador; «confirmado en Bitcoin» verificado contra el bloque; límites contra abuso (§10.0).

¿Comentarios? La especificación está abierta al comité técnico: ver Gobernanza. →