Build1 publisher3 min readPublished
Whether original invoice bytes survive decides original versus reconstructed PDF; SHA-256 verifies integrity
A dev.to dispute workflow keeps the original invoice bytes, checks them against a recorded digest that throws on mismatch, and labels anything re-rendered from today's template a reconstruction with its inputs pinned in a manifest.
The Engineer · Build desk

What happened
- A dev.to walkthrough tells dispute teams to preserve the original invoice PDF bytes and verify their hash, reserving a rendered replacement for cases where the original artifact is genuinely unavailable.
- The evidence package it describes stores the source PDF, the business inputs, the template revision, the font files, the locale, the timezone and the rendering tool version, with a SHA-256 digest beside the object.
- It keeps two paths apart: a byte-for-byte copy after verification when the original exists, and a replay of a controlled rendering recipe labeled as reconstructed when it does not.
- A replay records the Node.js 22 image digest used by the worker, the PDF library version, the font package checksums, and the renderer command and version in a manifest.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint The check throws instead of repairing, so a drifted file stops the dispute. A human has to account for why the bytes changed before the case moves.
- cost Pinning a worker image digest, library version and font checksums bills every invoice the service generates for a replay capability only the disputed few will ever use, and the invoicing team carries that storage and build discipline.
- decision Whoever opens the case has to classify it first, because a verified copy and a labeled reconstruction hand the reviewer two different kinds of object with two different standings.
Two renders of the same invoice can match on screen and differ in every byte. The post names where the drift comes from: object ordering, metadata timestamps, font subsets, compression and producer fields [2]. Every one of those fields is set by the writing toolchain. "PDF is a file format, not a promise that two visually similar files have the same bytes," the author wrote [3].
So the workflow verifies the digest before anything reads the file. The `verifySource` function reads the stored file, computes a SHA-256 over the bytes, compares it against the `expectedSha256` in the dispute record, and throws a digest mismatch error when the two disagree [8]. It then checks that the first five bytes are `%PDF-`, and throws if they are not [9]. Both branches throw. "A changed file is a new fact that needs review, not a formatting problem to hide," the author wrote [10].
The copy path is the cheap half. `copyVerifiedPdf` hashes the file before the copy and again afterwards and throws `evidence copy changed bytes` when the digests differ, so a case-management system gets a local working copy while the evidence vault stays untouched [13].
Replay is the expensive half. Money is serialized as integer minor units, line items sort by their stored sequence, the timezone is an explicit IANA name, and the exact font files are embedded [14]. A current-date helper is out, because the post says it guarantees drift [16].
The failure modes explain the pinning. A tax rate lookup performed during replay can pick up a later rule [18]. A database query without `ORDER BY` can move line items [19]. Font fallback changes glyph widths, and different widths change line wrapping and pagination [20]. PDF metadata can carry hidden state such as a creation time or a producer field [21].
The acceptance test for a replay compares page count, extracted text, bounding boxes and a rendered image checksum at a fixed DPI, and the post says to report the byte checksum next to those results even though it is expected to differ once the renderer rewrites metadata [17]. A checksum you expect to fail is an unusual line item in an acceptance test. Four comparisons plus the byte digest makes five reported results, and one of the five is expected not to match [22].
On the evidence here, re-rendering does not destroy anything by itself. "Identical visual output is not identical evidence," the author wrote [12]. The loss happens in the write path. A content-addressed key, `invoices/{invoiceId}/{sha256}.pdf`, makes an accidental overwrite obvious and lets a dispute worker fetch the exact object an auditor selected [6]. The original stays immutable, and a correction becomes a new artifact with a reason and a link to its predecessor [7].
The digest has a narrow job. It proves which bytes were examined; it does not prove the invoice's business truth. The input record sits beside it [5]. This is one practitioner's workflow published on dev.to, and it does not cite a regulator, a court rule or an audit standard [24]. If I were adding one piece of it first, it would be the digest check: a few lines of Node, and a changed file stops the case [8][10].
What to watch
- Whether case-management or billing vendors ship digest verification that fails closed by default, or whether it stays a hand-written check per team.
- Whether a published e-discovery or audit standard names byte preservation for invoices. That would move this retention scheme from one author's practice to a requirement.
- Whether PDF libraries expose a deterministic output mode with fixed metadata and stable object ordering. A replay could then match on bytes.