Los mismos bytes cada vez: PDF reproducibles
Spec: ISO 32000-2, §14.4ISO 32000-2 §14.4Spec: ISO 32000-2, §14.3.3ISO 32000-2 §14.3.3
De un vistazo
Sección titulada «De un vistazo»Construya un PDF a partir de las mismas entradas dos veces y esperaría el mismo archivo. La mayoría de las bibliotecas PDF no pueden prometerlo: reconstruya y compare, y los bytes se desvían. NextPDF puede fijar las dos cosas que se mueven, de modo que las mismas entradas produzcan los mismos bytes, cada vez.
Por qué importa
Sección titulada «Por qué importa»Una salida idéntica byte a byte no es una métrica de vanidad. Es la base que sustenta tres cosas que los equipos realmente quieren.
La primera es el cacheado. Si una compilación es una función pura de sus entradas, el hash de su salida es una clave de caché. Mismas entradas, mismo hash, sáltese el trabajo y sirva el archivo almacenado. Cuando los bytes vagan, el hash vaga, y la caché nunca acierta.
La segunda es la evidencia de manipulación. Una canalización que puede regenerar el archivo exacto que entregó puede demostrar, más tarde, que un documento archivado no se alteró: reconstruirlo, calcular el hash de ambos, comparar. Si aunque sea un byte difiere por culpa de un reloj incrustado, la prueba se pierde y se vuelve al «confíe en mí».
La tercera es una CI fiable. Una prueba contra archivo de referencia registra una salida buena conocida y falla cuando un cambio la altera. Esa señal solo es significativa si un motor sin cambios reproduce un archivo sin cambios. Si cada ejecución difiere en una marca de tiempo, el archivo de referencia es ruido, y el equipo aprende a ignorar una compilación en rojo: el hábito más caro en las pruebas.
La versión breve
Sección titulada «La versión breve»En el perfil determinista de NextPDF, los dos campos controlados por el motor que de
otro modo se desviarían entre compilaciones idénticas son las fechas y el /ID.
Esto presupone que el resto de la canalización ya es estable: las mismas entradas, y
una serialización que no varía por sí sola (más sobre esto abajo):
- Fechas incrustadas. El diccionario de información del documento lleva
CreationDateyModDate(Spec: ISO 32000-2, §14.3.3ISO 32000-2 §14.3.3), y los metadatos XMP las reflejan. Capture el «ahora» en el momento de la compilación y cada reconstrucción difiere. - El identificador de archivo. El array
/IDes un par de cadenas de bytes que identifican el archivo (Spec: ISO 32000-2, §14.4ISO 32000-2 §14.4), almacenado en el diccionario del tráiler (Spec: ISO 32000-2, §7.5.5ISO 32000-2 §7.5.5). Las bibliotecas suelen derivarlo de la hora actual más bytes aleatorios, así que es diferente en cada ejecución por diseño.
Fije ambos —una marca de tiempo fija y una semilla fija para el /ID— y la salida se
convierte en una función determinista de su contenido. Deje el contenido en paz y el
archivo es idéntico byte a byte. Es la misma disciplina que el proyecto Reproducible
Builds estableció para el software compilado, aplicada a la capa del documento.
Cómo lo aborda NextPDF
Sección titulada «Cómo lo aborda NextPDF»El determinismo en NextPDF es un objeto de configuración, no un truco de pruebas. El
motor expone un objeto de valor DeterministicSettings en el espacio de nombres
NextPDF\Core. Es final readonly, inmutable, y fija exactamente las dos fuentes de
desviación derivadas del reloj y del azar nombradas arriba: las fechas y el /ID.
Fijarlas elimina las dos fuentes de desviación más comunes, pero no garantiza por sí
sola una salida idéntica byte a byte. El resto del comportamiento de serialización del
motor —el orden de los objetos, la creación de subconjuntos de fuentes y los ajustes
de compresión— también debe ser determinista para que la salida se reproduzca, y
NextPDF mantiene eso estable por diseño.
Su constructor toma dos argumentos:
public function __construct( public DateTimeImmutable $timestamp, public string $fileIdSeed,) { // ...}$timestamp es el único instante fijo escrito en cada campo de fecha
—CreationDate, ModDate y su reflejo XMP—. Pase un DateTimeImmutable y el
documento deja de preguntarle al reloj de pared qué hora es. $fileIdSeed es la
entrada que fija el /ID del tráiler: una cadena hexadecimal de 32 caracteres. Dé la
misma semilla y el motor deriva el mismo identificador de archivo en lugar de
muestrear el reloj y una fuente aleatoria.
El objeto valida su propia entrada. La semilla debe ser exactamente 32 caracteres
hexadecimales; cualquier otra cosa se rechaza en la construcción con una
InvalidConfigException en lugar de producir en silencio un /ID de aspecto
distinto. Es la misma postura de negarse a adivinar que adopta el resto del motor:
una entrada ambigua falla ruidosamente en lugar de cambiar los bytes en silencio.
Con ambos fijados, la receta es la que el proyecto Reproducible Builds hizo familiar: reconstruirlo, compararlo, y la comparación está vacía.
- Fix the inputsThe same content, fonts, and settings that produced the original document.
- Pin the timestampOne DateTimeImmutable feeds CreationDate, ModDate, and the XMP dates — no wall clock.
- Pin the /ID seedA 32-character hex seed derives the trailer /ID instead of a clock-plus-random value.
- BuildThe output is now a pure function of content; the two moving parts are held still.
- Rebuild and diffRegenerate from the same inputs and compare bytes — an empty diff is the proof.
Ejemplo práctico
Sección titulada «Ejemplo práctico»Una forma pequeña y completa. Los ajustes se construyen una vez y se reutilizan, de modo que dos ejecuciones del mismo programa emiten el mismo archivo.
<?php
declare(strict_types=1);
use NextPDF\Core\DeterministicSettings;use NextPDF\Exception\InvalidConfigException;
// One fixed instant for every date field — never the wall clock.$timestamp = new DateTimeImmutable('2026-01-01T00:00:00+00:00');
// A 32-character hex seed pins the trailer /ID. Same seed, same /ID.$fileIdSeed = '0123456789abcdef0123456789abcdef';
try { $deterministic = new DeterministicSettings( timestamp: $timestamp, fileIdSeed: $fileIdSeed, );} catch (InvalidConfigException $e) { // A malformed seed (not exactly 32 hex chars) is refused here, // before any document is built — not silently coerced. error_log($e->getMessage());
throw $e;}
// Hand $deterministic to the document configuration. With both moving// parts pinned, building the same content twice yields identical bytes://// sha256(build_one) === sha256(build_two)La semilla es una entrada de compilación que usted controla, no un secreto. Almacénela junto al resto de su configuración de compilación. El punto es que esté fija, para que el identificador de archivo que produce también sea fijo.
Concepto erróneo habitual
Sección titulada «Concepto erróneo habitual»La primera trampa es «eliminé la marca de tiempo, así que ahora mi compilación es
reproducible». Normalmente no lo es, porque el array /ID es la más silenciosa de
las dos fuentes. Las fechas son visibles en un panel de metadatos y fáciles de
recordar; el /ID del tráiler es invisible para la mayoría de los lectores y se
regenera a partir del reloj y de una fuente aleatoria en cada ejecución. Una
compilación que fija solo las fechas sigue produciendo un archivo distinto cada vez.
Hay que mantener ambos quietos.
La segunda trampa es tratar el determinismo como una función de seguridad por sí
sola. Un /ID fijo hace un archivo reproducible; no lo hace firmado, y no
demuestra por sí mismo que dos compilaciones coincidan. Una comparación byte a byte o
un hash demuestra que las compilaciones coinciden; fijar el /ID solo elimina una
fuente de diferencia espuria. Y ninguna de esas cosas demuestra que un tercero
responda por el archivo. La reproducibilidad y la firma son capas complementarias, no
sustitutas.
Límites y fronteras
Sección titulada «Límites y fronteras»El determinismo fija las piezas móviles del propio motor. No fija sus entradas. Si
su contenido incrusta una marca de tiempo en vivo, toma una fuente que cambió en
disco o renderiza un valor que depende de la fecha actual, la salida cambia porque la
entrada cambió, y eso es correcto. DeterministicSettings elimina el no
determinismo del motor, no el suyo. Una compilación reproducible sigue requiriendo
entradas reproducibles.
| Edition | Availability |
|---|---|
| Core | Full support. |
| Pro | Not in this edition |
| Enterprise | Not in this edition |
Documentos relacionados
Sección titulada «Documentos relacionados»- Pruebas contra archivo de referencia: la técnica de CI que depende de una salida idéntica byte a byte, y por qué un motor determinista es su condición previa.
- Actualizaciones incrementales: cómo crece un
PDF al añadir contenido, donde el array
/IDvuelve a importar para relacionar un archivo con sus versiones anteriores. - Los metadatos y el paquete XMP: dónde viven las fechas incrustadas, y cómo el paquete XMP refleja el diccionario de información del documento.
- La anatomía de un archivo PDF: el
tráiler, la tabla de referencias cruzadas y dónde se sitúa el array
/IDen la estructura del archivo.
Glosario
Sección titulada «Glosario»- Idéntico byte a byte: dos archivos que coinciden exactamente, byte a byte. La forma más fuerte de «lo mismo», y la que un hash o una comparación pueden verificar.
/ID(identificador de archivo): el array de dos cadenas de bytes que identifica un PDF y sus versiones (ISO 32000-2 §14.4), almacenado en el diccionario del tráiler (§7.5.5). Normalmente derivado del reloj más bytes aleatorios, que es por lo que cambia en cada compilación sin fijar.- Diccionario de información del documento: la estructura que lleva
CreationDateyModDate(ISO 32000-2 §14.3.3). Una de las dos fuentes de no determinismo que una compilación determinista debe fijar. - Archivo de referencia: una salida buena conocida y registrada con la que una prueba se compara; significativa solo cuando un motor sin cambios reproduce un archivo sin cambios.
- Compilación reproducible: una compilación cuya salida es una función determinista de sus entradas, de modo que reconstruir a partir de las mismas entradas produce los mismos bytes. El término viene del proyecto Reproducible Builds para el software compilado.