Skip to content

Design guides for brands, campaigns, and vectors.GuidesReproducible export packages: manifests, hashes and receipts

Reproducible export packages: manifests, hashes and receipts

Six months after delivery, someone emails to ask whether the banner running on the site is the one you signed off. You can spend an afternoon reconstructing a Thursday from memory, or you can answer it in one line. This guide is how you set up the second option, at delivery time, in about two minutes.

Turn on packaging

Open a document's Export review and set up the outputs you're delivering. When the Destination is Downloads, a Package section appears with one checkbox:

Package files with manifest

The note under it is the honest summary: it downloads one ZIP holding the export files plus a separate manifest.json credit record, and it can't be reopened as a Gesso document. The primary button changes from Download 6 files to Download package.

With Chosen folder as the destination the checkbox doesn't appear. Folder export writes each file on its own, which is right for a build directory and wrong for a delivery you want to be able to identify later.

What the manifest records

manifest.json sits at the root of the ZIP. It's pretty-printed at two-space indent, which is a technical way of saying you can open it in any text editor and read it like a page.

FieldWhat it holds
schemaVersion1. The shape of everything below.
createdAtISO timestamp of when the package was assembled.
document.titleThe document's title at export time.
export.backgroundkeep or transparent. Which paper decision produced these files.
export.files[]One entry per file: filename, format (png, jpeg, svg), scale (1, 2 or 3), artboardName, and width/height in pixels where the output has a pixel size.
credits[]Pexels photo credits for images in the packaged files: provider asset id, creator name and URL, source URL, license name and URL, retrieval timestamp, and which filenames each credit applies to.

The field that earns its place most often is artboardName. It's the thread from a delivered file back to the thing in your document that made it. scale is the runner-up, because a 2× and a 1× export of the same artboard are otherwise two files that look identical in a folder listing.

What the packager refuses to do

Packaging isn't a zip utility with a JSON file dropped in. It stops rather than produce a manifest that tells a lie about its own contents.

  • The manifest's file list must match the package exactly, same names in the same order. A mismatch throws rather than shipping a manifest that describes a different set.
  • Credits must name files that are actually in the package. A credit pointing at a filename that isn't there is refused.
  • Duplicate names are refused case-insensitively, so Hero.png and hero.png can't both go in and quietly become one file on a case-insensitive filesystem.
  • Entry names can't contain path separators, can't start with a dot, and can't carry control characters. The package is flat by construction.
  • Hard ceilings: 100 entries including the manifest, and 128 MiB total.
  • Sizes are checked against the bytes, not against a plan. If a source blob changes size between being measured and being read, the build stops.

Failure is loud and total: The export package could not be prepared. Nothing was downloaded. You never end up with a half-written ZIP you don't know is broken.

Proving it months later

The manual package gives you three things to check against. The manifest. The ZIP's own per-entry CRC-32. And whatever hash you record at delivery time. Record that third one. It costs one command, and it's the only one of the three that a re-zipped or re-encoded file can't survive.

This next part is Terminal, and it's the only part of this guide that is. If that's not somewhere you spend time, hand these four lines to whoever handles your systems, or copy them literally. Everything before and after this section is in the app.

At delivery:

shasum -a 256 spring-campaign.zip | tee spring-campaign.zip.sha256
unzip -l spring-campaign.zip

Store that .sha256 line beside the package, wherever the changelog lives. Months later, against the file the client says they have:

shasum -a 256 spring-campaign.zip
unzip -v spring-campaign.zip
unzip -p spring-campaign.zip manifest.json

shasum answers "is this byte-for-byte the package I sent". unzip -v prints the stored CRC-32 for every entry, which catches a single file swapped inside an otherwise intact archive. unzip -p prints the manifest, which tells you which artboard and scale the disputed file came from.

If someone sends you a loose PNG rather than the package, hash it and compare it against the same-named entry pulled out of your ZIP:

unzip -p spring-campaign.zip hero@2x.png | shasum -a 256
shasum -a 256 ~/Downloads/hero@2x.png

Two matching digests end the conversation.

The stronger contract, where it applies

Gesso also builds a campaign package with a considerably tighter contract. It belongs to the automation runtime rather than to the Export review button, so if you're working by hand, the previous section is your whole workflow and you can stop here.

UNVERIFIED: I can't tell you whether an ordinary reader can trigger a campaign package build from the shipped web client without a paired automation session. What follows is read from the code. The way in isn't something this guide can promise you'll find in the interface.

That package differs in four ways worth knowing about, because together they describe what "reproducible" can mean when the whole path is machine-driven.

Per-file digests in the manifest. Its manifest.json follows contract version 1.0 and records, for every entry, the relative path, media type, byte count and a SHA-256. It also records the source document id, the source revision, a source fingerprint, and the platform profile's id, version and verification date, alongside any warnings and omissions the run produced.

One hash for the whole package. The output-core hash is a SHA-256 over every entry, each contributed as a big-endian 64-bit name length, the name, a 64-bit byte length, then the bytes, with entries sorted by name first. Length-prefixing means no combination of names and contents can be rearranged into a colliding stream, and sorting means the order you happened to hand the files over doesn't change the answer. The archive itself is stored uncompressed, so the bytes in the package are the bytes that were hashed.

Approval is bound to content. Planning a package returns a review hash over the source identity, the profile, the file records, the warnings and the omissions. Building it requires that hash back. If anything changed between the review and the build, the build fails with changed approval rather than producing a package nobody approved. Path collisions block the build too, instead of getting resolved quietly.

Receipts are append-once. A committed receipt records the contract version, an export id, a transaction id, the output-core hash and a completion time, in a local ledger. Committing the same transaction id again returns the receipt that's already there. Committing it with a different output-core hash is rejected as transaction replay changed. A receipt is a record of what happened, not a slot that later runs can overwrite.

Delivery checklist

  • Destination set to Downloads, and Package files with manifest checked
  • Outputs, scales and background settled before packaging, not after
  • shasum -a 256 of the ZIP recorded next to the changelog at delivery time
  • The .ges source document archived alongside the package, since the ZIP can't be reopened as one
  • Photo credits reviewed in the manifest, not just in the export panel
  • The package filed under a dated, versioned name so a second delivery never overwrites the first

All of it buys you one property. Every file you hand over traces back to a document, an artboard, a scale, a moment, and a digest. When the question arrives six months later, you answer it by running a command instead of trusting anyone's memory of a Thursday.

Common questions

In Export review, in the Package section: the checkbox reads Package files with manifest. It only appears when the destination is Downloads, since writing to a chosen folder writes the files individually.