1# Stamps: what they promise, and what they cannot23Two different things could be asked of a build's stamp, and they are4different on purpose (issue #5 §6,5[decision 8](decisions/0008-stamp-identity-is-an-aspiration.md)):6**canonical-content equality is mandatory; stamp identity is an aspiration.**7This file is why the aspiration cannot be an assertion — what a stamp can8promise, what it cannot by construction, and what is enforced instead.910## Why a stamp cannot be recomputed from a bundle1112A build's stamp is an order-independent SHA-256 over named part hashes13(`docs/format.md` §8.1). Five kinds of part go into it:1415| Part | Hash of | Recoverable from the bundle? |16| --- | --- | --- |17| `worlds/<slug>.json` | the encoded world payload | yes — read the entry, hash it |18| `worlds/<slug>.bin` | the packed locations | yes |19| `worlds/<slug>.text` | the encoded text payload | yes |20| `icons/<name>` | the icon's bytes | yes |21| `atlas.json` | the encoded manifest, with `version.stamp` and `version.createdAt` blank | yes — blank the two fields and re-encode |22| `tiles/<pyramid>` | **the pyramid's own derivation stamp** | **no** |2324The last row is the whole difficulty. A pyramid's part hash is not a hash of25the tiles in the archive; it is the stamp the tile pipeline computed when it26derived the pyramid, which already names the source rasters and the tool that27cut them. That value is written nowhere in the `.atlas` file. A reader holding28a finished bundle has every other part in hand and cannot invert SHA-256 for29the rest, so the sum is out of reach by construction, not by omission.3031This is a property of format v3 as frozen, not a defect to fix: putting the32derivation stamps into the manifest would restamp every bundle in every33library.3435## The second ceiling: two derivers never stamp alike3637A derivation stamp covers **the deriving code's own source hash**38(`docs/generate.md` §4.2). That is deliberate and load-bearing: changing how a39level is reduced, or where the content bounds land, must invalidate every40pyramid, and a stamp that watched only the archive would quietly keep serving41the old derivation.4243The consequence is exact and unavoidable: **two derivers that write44byte-identical tiles still stamp differently, because they are different45tools.** A library built end to end by two implementations carries the same46tiles and the same canonical content under different twelve-hex names.4748Both ceilings resolve the same way: a stamp is a **rebuild-cost** promise —49"nothing that made this has moved" — and never a *content* promise.50Reproducibility is proven where it is observable, at the tiles and at the51composed bundle.5253## What is enforced instead5455The proxies below run on every `make test`, and each of them would fail for56any change that could move a stamp:5758- **The stamp arithmetic itself.** `format/bundle/stamp_test.go` holds the59 part-hash format, the order independence, the short form, the capture-day60 rule and `VersionedFileName` to stated vectors.61- **Manifest byte-parity.** `TestCorpusManifestsReEncodeExactly`62 (`format/bundle/corpus_test.go`): each committed corpus manifest parses into63 `format/bundle` types and re-encodes to the committed bytes. The manifest's64 encoded bytes *are* a stamped part, so a schema that has moved by one byte65 is caught exactly where it would have changed the stamp.66- **Identity derivation, end to end.** `TestCorpusManifestsDeriveTheirIdentity`:67 the file name, short stamp and capture day each corpus build actually68 carries are re-derived from its manifest alone.69- **Every recoverable payload.** `TestCorpusLocationsRepackToTheRecordedPayload`70 packs each world's unpacked locations back into the recorded payload bytes,71 both directions; `TestCorpusInventoriesAgreeWithTheirHeaders` holds the72 per-pyramid tile inventories to what the manifests promise.73- **The registry fold.** `TestCorpusManifestsFoldAsALibrary`: the ordering a74 stamp participates in — capture time, policy revision, stamp, locator —75 folds the corpus builds the way a library would.7677What the committed corpus cannot carry is a whole `.atlas` archive — rasters78and all, they are hundreds of megabytes — so the checks that need a real file79in hand (part hashes against the wire, entry order, tiles stored uncompressed)80live in `make corpus-smoke`, the maintainer's non-gating walk of an installed81library ([testing.md](testing.md)).8283## Where the measurements live8485The corpus extractions under `testdata/corpus/bundles/` record two real86builds — the proof city and the planet — file name, stamp, counts, payload87hashes and tile inventories, which is what the tests above measure against.88The pre-rewrite reproduction measurements, fixture by fixture, are archived89with the tree that took them on the `golden-reference` tag; the divergences90accepted from them are91[decision 18](decisions/0018-divergences-from-the-reference.md).