Byte yang sama setiap kali: PDF yang dapat direproduksi
Spec: ISO 32000-2, §14.4ISO 32000-2 §14.4Spec: ISO 32000-2, §14.3.3ISO 32000-2 §14.3.3
Sekilas pandang
Bagian berjudul “Sekilas pandang”Bangun sebuah PDF dari input yang sama dua kali dan Anda akan mengharapkan berkas yang sama. Sebagian besar pustaka PDF tidak dapat menjanjikan itu — bangun ulang dan diff, dan byte-nya bergeser. NextPDF dapat mematok dua hal yang bergerak, sehingga input yang sama menghasilkan byte yang sama, setiap kali.
Mengapa ini penting
Bagian berjudul “Mengapa ini penting”Keluaran yang identik per byte bukanlah metrik kesombongan. Ia adalah fondasi di bawah tiga hal yang benar-benar diinginkan tim.
Yang pertama adalah caching. Jika sebuah build adalah fungsi murni dari input-nya, hash keluarannya adalah sebuah kunci cache. Input yang sama, hash yang sama, lewati pekerjaannya dan sajikan berkas yang tersimpan. Ketika byte mengembara, hash mengembara, dan cache tidak pernah kena.
Yang kedua adalah tamper-evidence. Sebuah pipeline yang dapat membangkitkan ulang berkas persis yang ia kirim dapat membuktikan, kemudian, bahwa sebuah dokumen yang diarsipkan tidak diubah: bangun ulang, hash keduanya, bandingkan. Jika bahkan satu byte berbeda karena sebuah jam tersemat, buktinya hilang dan Anda kembali ke “percaya saja”.
Yang ketiga adalah CI yang dapat dipercaya. Sebuah golden-file test merekam sebuah keluaran yang diketahui-baik dan gagal ketika sebuah perubahan mengubahnya. Sinyal itu hanya bermakna jika sebuah mesin yang tak berubah mereproduksi sebuah berkas yang tak berubah. Jika setiap jalankan berbeda pada sebuah timestamp, golden file itu adalah derau, dan tim belajar mengabaikan sebuah build merah — kebiasaan paling mahal dalam pengujian.
Versi singkatnya
Bagian berjudul “Versi singkatnya”Dalam profil deterministik NextPDF, dua field yang dikendalikan-mesin yang
selain itu akan bergeser di antara build yang identik adalah tanggal-tanggal dan
/ID. Ini mengasumsikan sisa pipeline sudah stabil — input yang sama, dan
sebuah serialisasi yang tidak bervariasi dengan sendirinya (lebih lanjut tentang
itu di bawah):
- Tanggal tersemat. Document information dictionary membawa
CreationDatedanModDate(Spec: ISO 32000-2, §14.3.3ISO 32000-2 §14.3.3), dan metadata XMP mencerminkannya. Tangkap “sekarang” pada saat build dan setiap build ulang berbeda. - File identifier. Array
/IDadalah sepasang byte string yang mengidentifikasi berkas (Spec: ISO 32000-2, §14.4ISO 32000-2 §14.4), disimpan di trailer dictionary (Spec: ISO 32000-2, §7.5.5ISO 32000-2 §7.5.5). Pustaka biasanya menurunkannya dari waktu saat ini ditambah byte acak, sehingga ia berbeda di setiap jalankan menurut desainnya.
Patok keduanya — sebuah timestamp tetap dan sebuah seed tetap untuk /ID — dan
keluarannya menjadi sebuah fungsi deterministik dari kontennya. Biarkan
kontennya tak berubah dan berkasnya identik byte-demi-byte. Ini adalah disiplin
yang sama yang ditetapkan proyek Reproducible Builds untuk perangkat lunak
terkompilasi, diterapkan pada lapisan dokumen.
Bagaimana NextPDF menanganinya
Bagian berjudul “Bagaimana NextPDF menanganinya”Determinisme di NextPDF adalah sebuah objek konfigurasi, bukan sebuah trik tes.
Mesin mengekspos sebuah objek nilai DeterministicSettings di namespace
NextPDF\Core. Ia bersifat final readonly, immutable, dan ia mematok persis
dua sumber pergeseran yang diturunkan-jam dan diturunkan-acak yang disebut di
atas: tanggal-tanggal dan /ID. Mematoknya menghilangkan dua sumber pergeseran
yang paling umum, tetapi itu tidak dengan sendirinya menjamin keluaran yang
identik per byte. Perilaku serialisasi mesin lainnya — pengurutan objek, font
subsetting, dan pengaturan kompresi — juga harus deterministik agar keluarannya
dapat direproduksi, dan NextPDF menjaganya tetap stabil menurut desainnya.
Constructor-nya menerima dua argumen:
public function __construct( public DateTimeImmutable $timestamp, public string $fileIdSeed,) { // ...}$timestamp adalah satu-satunya instan tetap yang ditulis ke setiap field
tanggal — CreationDate, ModDate, dan cerminan XMP-nya. Berikan satu
DateTimeImmutable dan dokumen berhenti bertanya kepada jam dinding sekarang
pukul berapa. $fileIdSeed adalah input yang mematok trailer /ID: sebuah
string heksadesimal 32 karakter. Berikan seed yang sama dan mesin menurunkan
file identifier yang sama alih-alih mengambil sampel jam dan sebuah sumber acak.
Objek itu memvalidasi input-nya sendiri. Seed harus persis 32 karakter
heksadesimal; apa pun selain itu ditolak pada saat konstruksi dengan sebuah
InvalidConfigException alih-alih diam-diam menghasilkan sebuah /ID yang
tampak berbeda. Ini adalah sikap menolak-menebak yang sama yang diambil sisa
mesin — sebuah input yang ambigu gagal dengan keras alih-alih diam-diam mengubah
byte-nya.
Dengan keduanya dipatok, resepnya adalah yang dibuat akrab oleh proyek Reproducible Builds: bangun ulang, diff, dan diff-nya kosong.
- 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.
Contoh praktis
Bagian berjudul “Contoh praktis”Sebuah bentuk yang kecil dan lengkap. Settings dikonstruksi sekali dan digunakan ulang, sehingga dua kali menjalankan program yang sama memancarkan berkas yang sama.
<?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)Seed adalah sebuah input build yang Anda kendalikan, bukan sebuah rahasia. Simpan ia di samping sisa konfigurasi build Anda. Intinya adalah ia tetap, sehingga file identifier yang ia hasilkan juga tetap.
Kesalahpahaman umum
Bagian berjudul “Kesalahpahaman umum”Jebakan pertama adalah “Saya menghapus timestamp-nya, jadi build saya sekarang
dapat direproduksi.” Biasanya tidak, karena array /ID adalah yang lebih senyap
dari kedua sumber itu. Tanggal terlihat di sebuah panel metadata dan mudah
diingat; trailer /ID tidak terlihat oleh sebagian besar pembaca dan
dibangkitkan ulang dari jam dan sebuah sumber acak di setiap jalankan. Sebuah
build yang hanya mematok tanggal tetap menghasilkan sebuah berkas yang berbeda
setiap kali. Anda harus menahan keduanya tetap diam.
Jebakan kedua adalah memperlakukan determinisme sebagai sebuah fitur keamanan
dengan sendirinya. Sebuah /ID yang dipatok membuat sebuah berkas dapat
direproduksi; ia tidak membuatnya ditandatangani, dan ia tidak dengan
sendirinya membuktikan bahwa dua build cocok. Sebuah perbandingan byte-demi-byte
atau sebuah hash membuktikan build-nya cocok; mematok /ID hanya menghilangkan
satu sumber perbedaan palsu. Dan tidak satu pun dari itu membuktikan bahwa
sebuah pihak ketiga menjamin berkasnya. Reproduksibilitas dan penandatanganan
adalah lapisan yang saling melengkapi, bukan pengganti.
Batas dan batasan
Bagian berjudul “Batas dan batasan”Determinisme mematok bagian-bagian bergerak mesin itu sendiri. Ia tidak mematok
input Anda. Jika konten Anda menyematkan sebuah timestamp langsung, menarik
sebuah font yang berubah di disk, atau merender sebuah nilai yang bergantung pada
tanggal saat ini, keluarannya berubah karena input-nya berubah — dan itu
benar. DeterministicSettings menghilangkan non-determinisme mesin, bukan
non-determinisme Anda. Sebuah build yang dapat direproduksi tetap memerlukan
input yang dapat direproduksi.
| Edition | Availability |
|---|---|
| Core | Dukungan penuh. |
| Pro | Not in this edition |
| Enterprise | Not in this edition |
Dokumen terkait
Bagian berjudul “Dokumen terkait”- Golden-file testing — teknik CI yang bergantung pada keluaran yang identik per byte, dan mengapa sebuah mesin deterministik adalah prasyaratnya.
- Incremental updates — bagaimana sebuah
PDF tumbuh dengan menambahkan, di mana array
/IDkembali penting untuk mengaitkan sebuah berkas dengan versi sebelumnya. - Metadata and the XMP packet — di mana tanggal tersemat hidup, dan bagaimana XMP packet mencerminkan document information dictionary.
- The anatomy of a PDF file —
trailer, cross-reference table, dan di mana array
/IDberada dalam struktur berkas.
Glosarium
Bagian berjudul “Glosarium”- Byte-identical — dua berkas yang cocok persis, byte demi byte. Bentuk terkuat dari “yang sama”, dan yang dapat diverifikasi oleh sebuah hash atau sebuah diff.
/ID(file identifier) — array dari dua byte string yang mengidentifikasi sebuah PDF dan versi-versinya (ISO 32000-2 §14.4), disimpan di trailer dictionary (§7.5.5). Biasanya diturunkan dari jam ditambah byte acak, itulah sebabnya ia berubah di setiap build yang tidak dipatok.- Document information dictionary — struktur yang membawa
CreationDatedanModDate(ISO 32000-2 §14.3.3). Salah satu dari dua sumber non-determinisme yang harus dipatok oleh sebuah build deterministik. - Golden file — sebuah keluaran yang diketahui-baik yang direkam yang dibandingkan oleh sebuah tes; bermakna hanya ketika sebuah mesin yang tak berubah mereproduksi sebuah berkas yang tak berubah.
- Reproducible build — sebuah build yang keluarannya adalah sebuah fungsi deterministik dari input-nya, sehingga membangun ulang dari input yang sama menghasilkan byte yang sama. Istilah ini berasal dari proyek Reproducible Builds untuk perangkat lunak terkompilasi.