The Flow series introduced the Secure Job Envelope as a file two shops can both check, and the post after it explained why it's shaped the way it is. Both called the job's identity a fingerprint and left it there. Technically it's a hash: a short, fixed-length code computed from the job's contents. Change any byte that goes in, and the code comes out different.
This series goes a layer down, one piece at a time, for the people who have to implement it or decide whether to trust it: shop owners with a developer, ERP vendors, and engineers who'd rather read the mechanism than the pitch. Everything here is in the public spec, and where a claim is backed by a test, the claims register names the test.
First piece: the fingerprint itself. What goes in, what stays out, and why the line is drawn where it is.
The quoteable body
The hash covers exactly nine fields, and nothing else. The spec calls this set the quoteable body:
spec, traveler_id, revision, created_at,
buyer, part, need_by, incoterms, itar
Read it as a sentence: this version of this job, composed by this buyer on this date, for this part, needed by this date, on these shipping terms, with or without ITAR data. That is the whole of what a seller needs to put a price on. The name is literal — it's the body a quote is about.
Two of those fields are themselves closed objects, and the reference implementation's hash.ts is the exact list. buyer is an organization record: name, org id, city, region, contact, certifications, and its ITAR declaration. part is the part as drawn: family, part number, description, drawing revision, material (spec, form, thickness), quantity with price breaks, processes, finish, tolerances, notes. Inside both, the rules are strict: optional fields that are absent are dropped before hashing, empty strings and empty arrays are dropped, need_by and incoterms become null when missing, and itar defaults to false. So a job with processes: [] and a job that simply doesn't mention processes hash to the same thing. There is one way to say "nothing here," not three.
Notice what that means for privacy: the buyer's name, city, and contact are in the hashed body. The fingerprint doesn't reveal them — a hash isn't reversible — but the file does, which is why the format has a separate, tested rule for what may be said about a job before both sides agree to trade. That's part 8.
Closed is the important word. Extra keys on an in-memory object never enter the hash. An implementation that adds its own internal fields — a UI flag, a sync timestamp — can't accidentally change the job's identity by doing so. The register's C2 row is the test that proves it: a field that's in the set changes the hash when it changes; a key that isn't in the set doesn't, however much it changes.
Why traveler_id and created_at are inside
It's tempting to hash only the part and the terms, so two identical requests would get the same fingerprint. The spec doesn't. The id and the composition timestamp are in the hash, so a job composed on Tuesday and an identical one composed the following Tuesday are two different travelers with two different fingerprints.
Whatever else that choice does, it matches how a shop thinks. A quote is a promise about this job, not about any job that happens to look like it. Two RFQs for the same bracket, a month apart, get two quotes, and nobody wants the first price riding onto the second request because the part number matched.
What's deliberately outside
Everything the job gains after it's priced sits outside the hash:
- the quotes themselves
- the award
- the operations list (the routing)
- the ship-to
- the as-built record
This is the decision that makes the lifecycle work. A quote binds to the hash. If the award were inside the hash, awarding a quote would change the hash and un-bind the quote you just awarded. So the traveler can climb from quoteable to awardable to executable — gaining a quote, an award, a routing, a dock address — without the fingerprint moving, and the seller's quote still matches exactly what they priced, all the way to delivery.
The flip side is the rule people push back on, which the bracket walkthrough showed in action: amending anything in the quoteable body bumps the revision and marks every existing quote stale. Change the shipping terms from FOB to EXW and the body changed, so the hash changed, so the quotes are about a job that no longer exists as written. The spec doesn't try to judge which changes "matter." Every change to the body is a new revision, and a seller re-confirms against the job as it is now.
The register's L1 row covers both halves: a quote binds only to the exact revision it priced, and amending marks every quote stale.
The second digest
If the award and ship-to are outside the hash, what stops someone from changing them in transit?
A second digest. When a traveler travels, it goes in an archive — {traveler_id}.traveler.zip — alongside a small manifest, META.json. The manifest carries the quoteable hash and a SHA-384 over the whole traveler.json file, every byte, lifecycle fields included. The spec makes that full-file digest required, not optional, precisely because the quoteable hash alone would let the award or the dock address be altered under an unchanged fingerprint. On import, the receiver re-canonicalizes the body, recomputes both digests, and refuses the archive if either disagrees.
Here is the honest limit, stated in the register as G7: both digests are unkeyed. No secret key goes into them, so anyone can compute them. They catch corruption and naive tampering, and they guarantee the archive is internally consistent. They do not stop a party who re-seals the archive from recomputing them. Proving who authored the job is a different mechanism — signatures — and in 0.1 the authorship signature covers the quoteable body, not the lifecycle fields. That's part 5 of this series, and it's listed as a gap, not hidden as a feature.
Three things the fingerprint is not
It is not a signature. A hash tells you two files say the same thing. It doesn't tell you who wrote either of them. Anyone who can compute SHA-384 can produce a valid hash for a job they made up with your company's name on it. Authorship is a separate layer (ML-DSA-87 signatures, verified against a signed directory of shop keys), and the spec is careful to say so in the same paragraph it defines the hash.
It is not secrecy. The traveler is a plaintext JSON file. The hash hides nothing; holding the file is the reveal. Confidentiality comes from sealing the file into an encrypted envelope for transport, which is parts 6 and 7. The repo's disclosure page says this in its first heading — the file is not encrypted — because it's the thing people most often assume a hash does.
It is not the drawing. The part record carries the drawing revision, not the drawing file. Two shops can agree they're looking at rev C. The envelope doesn't yet carry rev C itself. That's in the list of gaps at the end of Flow, Part 7.
Why SHA-384
Many content-addressed systems use SHA-256. The spec picked SHA-384 and explains why: the fingerprint has to stay unforgeable against an adversary with a quantum computer, and SHA-256's 128-bit collision margin erodes under quantum collision search (finding two inputs with the same hash). SHA-384 keeps at least 128 bits of collision resistance and at least 192 bits against preimage attacks (finding an input that matches a given hash), even allowing for Grover's quantum search algorithm. The cost is a longer string to look at — sha384: and 96 hex characters.
The more important decision was when. It was fixed before the first release, so no fingerprint anyone has ever computed will need to migrate. A hash migration on a format meant to carry jobs for years would be a disaster; the way to avoid it is to not need one.
What to take from this
If you implement the format: hash the nine fields, in canonical form (the one agreed byte layout the next post covers), and nothing else; drop empties; treat extra keys as invisible; require the full-file digest on import. If you're deciding whether to trust it: the fingerprint tells you same job, same revision, provably, on both ends. It doesn't tell you who sent it or keep it private. Those are separate layers, and the next posts take them one at a time — starting with the unglamorous question of how two programs written in two languages produce the identical bytes to hash in the first place.