# A file name does not name a file Source: https://truecopy.dev/blog/a-file-name-does-not-name-a-file/ truecopy 2.0.15 gives every document it opens the SHA-256 of its bytes. Why a reading kept for years needs it, and why it is taken before pdf.js touches the file. A reading outlives the file it came from. A row read off a statement, a figure a model cited from an annual report, an anchor that says page 4 prints `1 234`: each is worth something later only if somebody can go back to the page and look. Until 2.0.15, the only thing a truecopy document said about its source was the name it had been given. ## A name is a label on the outside `statement-2026-09.pdf` is what the issuer called the file, or what a download folder called it, or `download (1).pdf` when it was fetched twice. Both directions go wrong. An issuer corrects a document and publishes it again at the same address under the same name, so one name ends up meaning two files with different figures. And one file reaches you under two names, so a check that compares names sees two documents where there is one. A reading that points at a name points at whichever file carries that name today. Nothing in the reading can tell that the file changed underneath it. ## 2.0.15 names the bytes `openDocument` now sets `document.digest`: the SHA-256 of the exact bytes it was given, in lowercase hex. The two PDFs below were built for this post and differ by one printed figure, `1234` in the first and `1243` in the second. Played against truecopy 2.0.15: ```js import { openDocument } from 'truecopy'; const a = await openDocument(new File([september], 'statement-2026-09.pdf')); const b = await openDocument(new File([september], 'download (1).pdf')); const c = await openDocument(new File([corrected], 'statement-2026-09.pdf')); a.digest === b.digest; // true: the same bytes under two names a.digest === c.digest; // false: the same name, one figure apart ``` Keep the digest beside the reading, and keep the file under its digest, and the reading points at one set of bytes for as long as the archive lasts. Web Crypto computes it, in a browser as in Node, so the library gained no dependency for it. A document built from a string with `documentFromText` carries no digest at all. There is no file behind a paste, and a hash of the text would claim one. ## Why it is taken before the engine sees the file This is the part that a test had to pin down, because getting it wrong looks exactly like getting it right. pdf.js takes the buffer it is handed. Measured with the build truecopy loads by default, in Node: a 580-byte PDF is read into an `ArrayBuffer`, pdf.js opens it, and the same buffer then reads 0 bytes. A digest taken at that point is the SHA-256 of nothing: ``` e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ``` It is 64 hex characters, like any other digest. It would be the same for every PDF ever opened, so every reading would claim the same source, and no check would object, because there is nothing malformed about it. A field that always holds a plausible value is the hardest kind of wrong to notice. So `openDocument` hashes the bytes first and hands them to the engine after. The test that holds it opens one PDF under two names and compares both digests with a hash of the bytes taken outside the library. A digest of the empty buffer would fail it on the first line. ## What it does not do It does not say a file is genuine. It says these are the bytes the reading was made on, not that the issuer wrote them. It moves no reading. Rows, cells and findings are what they were in 2.0.14, so an application that does not read `digest` sees no change, and there is nothing for a consumer to re-measure. The reference is on the [open page](https://truecopy.dev/docs/open/#the-digest-names-the-bytes), the release notes are on [the versions page](https://truecopy.dev/versions/), and the change itself is [pull request #101](https://github.com/truecopydev/truecopy/pull/101). Map of this site for a model: https://truecopy.dev/llms.txt Every page in one file: https://truecopy.dev/llms-full.txt