§2 The pinned toolchain
Recorded from the build that produced the committed corpus bytes and the digests in manifest.json.
| Component | Pinned value | What it decides |
|---|---|---|
| Pillow | 12.3.0 | Decoding for every container; the orientation transposes; palette expansion; Image.alpha_composite; mode conversion at quantisation |
| libjpeg | API level 6.2, as bundled in the Pillow 12.3.0 wheel (libjpeg-turbo) | IDCT precision and chroma upsampling — the two disagreements E12 names by name |
| libwebp | 1.6.0 | WebP lossless decode |
| libtiff | 4.7.1 | TIFF strip layout and LZW decode |
| zlib | 1.3 | PNG inflate |
| littleCMS | 2.19, reached through PIL.ImageCms | The ICC → sRGB transform: intent handling, black point compensation, rounding |
| NumPy | 2.4.6 | The uint8 [H, W, 3] C-contiguous buffer whose tobytes() is hashed |
| Python | 3.11.15 (CPython) | Recorded for completeness; nothing in the decode path depends on it |
Other Pillow versions must be evaluated, not assumed. Nothing in this profile predicts the behaviour of Pillow 12.4, 13.0, a system-libjpeg build, a Pillow-SIMD build, or a distribution package that links different codecs. Each is a candidate implementation under E12: run the corpus, and if every digest reproduces, that build is conformant for verd-pillow/1 too. If any digest moves, it is not, and no amount of version-number proximity substitutes for the run. This is the practical content of E12 — "conformant decoder" names a test, not a dependency range (VERSIONING.md §3.4, condition 3).
Pillow does not expose libjpeg-turbo's own version string through PIL.features; features.version("jpg") reports the libjpeg API level (6.2), not the turbo release. An implementation that wants to match this profile by construction rather than by evaluation should pin the Pillow wheel, which carries the codec builds with it.
