1# The enrich lane23Enrich makes volumes richer. It takes a volume the [generate lane](generate.md)4composed — or the several readings of one volume that several sources produced —5and writes a **new build of the same volume**: same slug, new stamp, more in it.67```8readings of one volume ──▶ [ merge → national → stdicons → lenses ] ──▶ a richer build9 generate, one per source the curated queue beside the old one10```1112Five laws hold everywhere in this lane.13141. **A volume in, a new build of the same volume out.** Same slug, new stamp.152. **Nothing published is ever mutated.** The registry is a directory of16 immutable files and a fold over them, so publishing is a new file landing17 beside the old one.183. **Nothing a source said is removed or rewritten.** An enricher fills what is19 empty. It has no way to express anything else — see §3.204. **Every contribution is ledgered**, and a gate audits the ledger before a21 build is written.225. **No change, no build.** An enrichment that finds nothing to say leaves the23 library exactly as it was.2425This document is normative for the enricher interface, the contribution format,26the ledger vocabulary, the curation data, and the scoring table. It is the prose27twin of `internal/enrich`.2829---3031## 1. The seam: how `generate ⊕ enrich` is joined3233The two pipeline lanes never import each other (issue #5 §3.2, enforced by34`tools/depcheck`). Generate owns the interchange document; enrich owns its own35model of a volume under enrichment. Both carry the same information, because36both are shaped by what the format needs.3738The ⊕ is performed by the one binary that holds both:3940```41atlas enrich42 │43 ├─ generate: archive ──▶ doc.Document, one per source reading44 ├─ cmd/atlas: doc.Document ──▶ enrich.Volume (adapt.go, a copy)45 ├─ enrich: the curated queue runs, contributions are applied46 ├─ cmd/atlas: enrich.Volume ──▶ doc.Document + ledger (adapt.go, a copy)47 └─ generate: compose.Compose(document, ledger, revision) ──▶ a .atlas file48```4950**Why this design and not the alternatives.** Three shapes were possible:5152- *Enrich composes.* Rejected: enrich would import `generate/compose`, which the53 lane matrix forbids, and the forbidding is not bureaucratic — it is what keeps54 either lane replaceable without the other.55- *Enrich emits doc-level additions the CLI feeds back.* This is what happens,56 but the additions are expressed in **enrich's own vocabulary**, not the57 document's, so an enricher never learns the document schema.58- *A shared model in `format/`.* Rejected: the volume-under-enrichment is not a59 format concept. The format describes a written bundle; this model describes a60 build in progress, including a grid to measure distances in and a ledger that61 is not final yet.6263The adaptation is a mechanical copy in both directions and is held to identity64by `TestAdaptationRoundTrips`: a document adapted and adapted back is the same65document, byte for byte. That test is also the mechanical form of law 5 — an66empty queue cannot produce a different build, because it cannot produce a67different document.6869The ledger does not travel through the document. A document is what **one70source** has to say, and a ledger is provenance about a composition, so the71accounts reach the payload through `compose.Options.Ledger` — already72serialized, spliced in verbatim. Composition never learns what a matched pair or73a held feature is, which is what lets the ledger vocabulary below belong wholly74to this lane.7576**The seam has a second half, upstream.** A warped lens — one source's raster77resampled into another's world — is derived by `atlas tiles`, in the generate78lane, and its one input the archive does not hold is the alignment this lane79fits. So `cmd/atlas/warp.go` fits it here and hands the deriver six numbers,80exactly as `adapt.go` hands the queue a volume. Nothing is written twice: the81deriver needs a fitted transformation, not the ability to fit one, and it is the82*same* fit the merge folds features by — which is why a warped raster and the83features drawn on it can never disagree about where the ground is. See84`docs/generate.md` §4.4.8586---8788## 2. Winning the registry fold8990An enriched build has to be the one a library serves, deterministically, without91mutating anything.9293The registry's ordering is **creation time, then policy revision, then stamp,94then locator** (see [format.md](format.md)). Creation time is the newest capture95the build was made from and never the build clock — a format invariant this lane96does not touch. An enrichment of the same captures therefore ties on creation97time with the plain build beside it, and the revision has to decide.9899**The mechanism.** The revision is one integer carrying two policy numbers:100101```102revision = enrichPolicy × RevisionSpan + generatePolicy RevisionSpan = 100103```104105A plain single-source build writes its generate policy revision alone, which is106the same number with an enrich policy of zero. So `9` is generate policy 9107unenriched, and `109` is generate policy 9 enriched under enrich policy 1.108109Three properties made this the choice:110111- **Deterministic.** The number is a pure function of two compiled-in112 constants. Nothing scans the library, nothing reads a clock, and the stamp —113 which covers the manifest, revision included — stays a pure function of the114 inputs. A mechanism that read the serving build's revision off disk and added115 one would have made the stamp depend on which machine built it, which breaks116 the format's first invariant.117- **Total.** Every enriched build of one capture outranks every plain build of118 that capture, with no tie for the stamp to break arbitrarily.119- **Honest about which axis wins.** Packing two axes into one integer makes one120 dominant, and this picks the enrich axis: within one set of captures an121 enriched build is a *superset* of the plain build, so serving the plain build122 over it would lose data the library already holds. A generate policy change123 takes effect for an enriched volume when the pipeline is re-run — which is how124 a policy change reaches a merged volume anyway, because enrichment is a125 pipeline stage and not a separate ritual.126127`BuildRevision` refuses a generate revision that does not fit the span rather128than wrapping into the next enrich band. Widening the span is a deliberate129restamp of every enriched build, not an accident.130131---132133## 3. The `Enricher` interface134135```go136type Enricher interface {137 Name() string138 Declares() []string139 Enrich(v *Volume, ctx Context) (Contribution, error)140}141```142143- **`Name`** is how curation orders it and how a log line names it.144- **`Declares`** lists the semantic-conventions keys this enricher may write.145 An attribute it writes that is not declared here fails the build, so what an146 enricher can say about a volume is visible without reading it. A declared key147 the registry does not know fails the build too.148- **`Enrich`** reads the volume and returns additions. **It must not modify the149 volume**: the driver applies contributions, so every change goes through the150 ledger and the gate. The merge enricher, which needs to see the effect of one151 donor before folding the next, works on a clone of its own.152153`Context` is everything else an enricher may consult, and every field is data154the caller supplies: the donor readings, the evidence base, the lens offers, the155curation tables, the run's logger. An enricher never opens a file, never reaches156the network, and never learns which source anything came from except through a157ledger it is handed.158159### 3.1 The contribution, and its canonical form160161A contribution is a flat, ordered list of operations, and it has a **canonical162serialized form** — that form is the contract, and the Go value is a convenience163over it. It can be written to a file, diffed between two runs, replayed against164the same volume years later, or read by a reviewer who wants to see what an165enricher would do before it does it. `Digest()` is its SHA-256, which is what a166log line names a contribution by.167168```jsonc169{170 "contribution": "atlas-enrich-contribution",171 "version": 1,172 "enricher": "merge",173 "volume": "cyberpunk-2077",174 "ops": [175 { "op": "set-prose", "world": "night-city", "feature": 123, "value": "…" },176 { "op": "set-attr", "world": "night-city", "feature": 123,177 "entity": "feature", "key": "atlas.geo.lon", "value": "-1.5" },178 { "op": "add-feature", "world": "night-city", "collection": 90,179 "newFeature": { … } },180 { "op": "add-collection", "world": "night-city", "newCollection": { … } },181 { "op": "add-world", "newWorld": { … } },182 { "op": "set-icon", "world": "night-city", "collection": 90,183 "key": "std--maki-monument", "value": "std--maki-monument.svg" },184 { "op": "add-asset", "asset": { "key": "…", "file": "…", "data": "<base64>" } },185 { "op": "set-lens", "world": "night-city", "lens": { … } },186 { "op": "ledger", "world": "night-city", "account": { … } }187 ]188}189```190191**The vocabulary is deliberately additive.** There is no operation that removes192a feature, empties a collection, or deletes a world, so law 3 is not a rule an193enricher has to remember — it is a property of what an enricher is able to194express. The two operations that touch existing data refuse to overwrite:195196| operation | what it refuses |197| --- | --- |198| `set-attr` | a key already set to a different value; an unregistered key; a key on the wrong entity; a value outside its vocabulary |199| `set-prose` | a feature that already has prose |200| `add-feature`, `add-collection` | an identifier already spoken for in that world |201| `add-world` | a slug the volume already pictures |202| `set-icon` | a collection that already names artwork |203| `set-lens` | repointing an existing lens at another tile set (a re-derivation of the same tile set updates in place, which is how a new derivation stamp lands) |204| `add-asset` | a file name already carried with different bytes |205206A refused operation fails the whole application: half an account is not an207account.208209### 3.2 The ordered queue210211Enrichers run in an order declared in `internal/enrich/curation/curation.json`,212never in an order that emerges from a map iteration or a registration side213effect. `enrich.Queue` is strict both ways — a queued name nothing implements214fails, and an implemented enricher nothing queues fails — because an enricher215running in an order nobody wrote down is folklore.216217The order today, and why:218219| # | enricher | why here |220| --- | --- | --- |221| 1 | `merge` | first, so everything after sees the whole volume: a collection folded in from another reading wants an icon and a score like any other |222| 2 | `national` | after merge, so a feature another reading contributed can also learn which subwatershed its ground lies in |223| 3 | `stdicons` | after merge — the whole reason it is post-composition: a merged-in collection that declares a standard icon resolves it too |224| 4 | `lenses` | last, because a picture attaches to a ground and the grounds are not all known until everything that could contribute one has run |225226Each enricher sees the volume as the ones before it left it.227228### 3.3 The evidence ethic229230**Silence over plausibility.** An enricher that cannot be sure says nothing and,231where the doubt is about a particular thing, writes down what it was unsure232about. A held feature with its reason recorded costs a curiosity; a wrong claim233costs a place, and a reader cannot tell a plausible claim from a true one.234235There is exactly one deliberate exception, `stdicons`: a standard-icon236declaration the vendored library cannot answer **fails the build**. The237difference is who made the promise. A membership join guesses about the world; a238standard-icon declaration was written by a translator author in this repository,239and an unanswerable one is a typo that should be heard about while it is one240table edit old.241242---243244## 4. The ledger245246Every contribution to a world is written down in one vocabulary, whatever made247it. The vocabulary is five words, on purpose: a ledger nobody can hold in their248head is a log, and a log is not an account.249250| word | meaning |251| --- | --- |252| **matched** | the world already held this, and the two are one thing |253| **added** | the world did not hold this, and now does |254| **adopted** | an added thing joined a collection the world already had |255| **held** | the contribution could not decide, and says why |256| **rejected** | the contribution refused this outright, and says why |257258Each world carries one **origin account** — where its ground came from and what259it held when it got there — plus one account per contribution folded in since.260The origin account is opened before the queue runs, because after an enricher261has run there is no way to find out what the world arrived with.262263```jsonc264{265 "source": "IGN Wiki", "slug": "ign-wiki",266 "donorFeatures": { "point": 368, "path": 0, "area": 0 },267 "matched": [ { "d": 2123771580, "w": 1452480306, "px": 20,268 "e": true, "took": ["atlas.note.text"] } ],269 "added": 37,270 "adopted": [ { "d": 2041587856, "into": "clothing-vendor" } ],271 "held": [ { "d": 1775561692, "t": "Arroyo Clothes Shop",272 "why": "beside \"Clothing Vendor\" in the same category; names disagree" } ],273 "rejected": [],274 "alignment": "99 anchors, median 26.0px, p90 52.0px, worst 67.4px"275}276```277278The key order above is the wire order and it is frozen. An account rides a world279payload, a world payload is a stamped part, and re-spelling one restamps every280build in every library — so the order is part of the format's surface rather than281a Go struct's declaration order that happens not to matter.282283The held and rejected reasons are **data, not messages about code**: they are284written into every merged bundle already published, and a reader comparing two285builds' accounts compares these strings. Their vocabulary is one rewrite behind286the rest of the lane — a feature is a "pin", a collection a "category" — and287that is the price of not rewriting what every existing bundle says about itself.288289### 4.1 The gate290291`GateAccount` and `GateWorld` fail the build rather than let a bundle be written292that quietly lost something — or quietly agreed too much:293294- every offered feature of every kind is accounted for: matched + added + held295 points + rejected must equal the offered points, and every offered shape must296 appear in the held ledger (shape features do not merge yet);297- every match is one-to-one — a serving feature matched by two donors is a place298 counted twice;299- every attribute a take claims answers to the conventions registry, and the300 older `enriched` flag says exactly what the takes say;301- one identifier space per world across every kind;302- the world holds exactly as many features of each kind as its accounts claim:303 what it opened with, plus what every account says it added.304305---306307## 5. The enrichers308309### 5.1 `merge` — two readings of one volume310311The newest capture serves; the others are folded into it.312313Each donor world is aligned into the serving world by the transformation their314shared named places determine (`internal/enrich/align`): pairs formed only from315names that are unambiguous in **both** readings, at least 12 of them, solved by316least squares per axis, trimmed twice to nine tenths to shed the places one side317simply pinned sloppily, and **refused outright** when the median residual still318exceeds 96px. A fit that will not close is a merge that does not happen; the two319readings stay apart in separate builds, which is exactly where they were before320anybody tried.321322A donor world with no counterpart by slug is tried against every world the323volume already draws — sources do not divide the world the same way, and a324shared slug is only the cheapest evidence of shared ground. A world nothing325pictures joins whole, artwork and all, opening its own origin account.326327Resolution, per donor feature, in world pixels of the volume's own square:328329| distance to the nearest same-named serving feature | outcome |330| --- | --- |331| ≤ 160px (`matchRadiusPx`) | **matched** — the same place |332| ≤ 320px (`separateRadiusPx`) | **held** — too far to merge, too near to double |333| beyond, or no such name | fall through |334335A name one reading spells inside the other's — "Northside Apartment" inside336"Northside, Watson Apartment" — counts as the same name when the two share a337collection **and** the shorter name carries at least two words: a bare338"Apartment" must not roam the world for a long-named cousin. A serving feature339another donor already matched cannot be matched again. Falling through, a340donor feature within `max(48px, 2 × the fit's p90)` of a serving feature in the341same collection is **held** — proximity alone never merges. A feature the342alignment puts outside the world square is **rejected**. Everything left is343**added**: into the serving collection where the two spell the same concept344(**adopted**), and otherwise under a collection of its own, filed under a group345named for its source.346347**Carried artwork** travels under two names on two rules, because they answer348different questions. The *file* the bytes land in always takes the source's tag349when a contribution goes into a ground the serving reading already draws: a350donor's picture may never be written where the serving volume keeps its own. The351*key* a collection names its artwork by stays the donor's own — there is nothing352to displace unless the serving volume already names different artwork with it,353and only then is the key tagged too.354355Collections meet under their **merge identity**: the `atlas.collection.key` the356payload carries, then the key the source gave the collection, then its artwork357key. The curated equivalents that produce it live in the generate lane's358curation, and curation speaks them over a reading *before* it is folded into359another — `curation.Tables.Declare`, called by the ⊕ right after translation.360Composition declares the same thing later for a document that never passed361through the enrich lane, but later is too late for a merge: a document whose362equivalence had not been declared yet would be merged on artwork keys alone,363which is the honest fallback for a source nobody has curated and quietly the364wrong answer for one that has been.365366**Per-key attribute policy.** Serving-wins is the default: what the serving367reading says stands. The keys listed under `merge.attributes.donorFillsEmpty` in368curation are *donor-fills-empty* — the serving side keeps what it has and takes369only what it lacks — and every take is ledgered by key:370371| key | why |372| --- | --- |373| `atlas.note.text` | a description; the rule this policy was invented for |374| `atlas.geo.lat`, `atlas.geo.lon` | true coordinates fill in the same way words do |375| `atlas.icon.std` | a serving collection with no artwork of any kind takes the donor's declaration |376377### 5.2 `national` — the hydrologic membership join378379A city's zones and trails are curated by the city; the subwatersheds under them380are surveyed by the USGS. Where a capture carries both, each piece of the city's381ground can be told which subwatershed it lies in — one sentence for the card and382the twelve-digit code beside it as `atlas.hydro.huc12`.383384Fetching stays in the generate lane (fetching is crawling). **The evidence base385travels in the capture**, so the join re-runs against the archive without386refetching, which is what lets the sentence be re-curated. The evidence reaches387the enricher through `Context.Evidence` under the name `hydro/huc12.json`:388389```jsonc390{391 "evidence": "atlas-enrich-evidence",392 "version": 1,393 "kind": "hydro.huc12",394 "space": "world", // the volume's own projection — the only space v1 reads395 "units": [ { "code": "170703010801", "name": "Deschutes Junction",396 "rings": [ [ [ [lng, lat], … ] ] ] } ]397}398```399400`space` is `world` because a claim is decided in the space the volume's features401are in, and whoever writes the evidence has already projected the survey into402the window it was clipped to. An evidence base that carries nothing about a403volume is not an error: it is a volume nobody surveyed, and the enricher says404nothing about it.405406**The judgement.** Each piece of a feature's geometry volunteers an anchor — the407centroid of its largest outer ring, or the middle vertex of its longest line —408plus an even sample of up to 16 boundary vertices. The claim is made **only when409every volunteered position lands in the same unit**. A trail that crosses a410divide says nothing; a zone whose boundary leaves the surveyed extent says411nothing. The anchor alone would let a citywide polygon claim whichever412subwatershed its centroid happened to sit in; sampling the boundary catches the413spread, and the cost stays flat however detailed the ring.414415The survey makes no claims about itself: `national.evidenceCollections` in416curation names the collections a national capture contributed to a volume, and417the join never writes a membership onto one of them. Point features are not418joined. A feature that already carries the key is left alone, and a feature that419already has prose keeps it — the claim still lands as the key, because the420sentence is a courtesy and the code is the knowledge.421422### 5.3 `stdicons` — the standard glyph library423424A collection that ships no artwork may name one from a standard set425(`atlas.icon.std = "maki/monument"`). This resolves the promise to bytes: the426glyph lands in the volume's icon set as `std--maki-monument.svg`, a name that427spells its provenance so a bundle listing reads honestly and a source's own428artwork can never be shadowed by it. The application never learns the library429exists — a resolved standard icon is one more asset in a bundle's icons tree.430431It runs after merge so merged-in collections resolve too. Only point432collections, only where the slot is empty, one asset however many collections433name it. Only the names some translator actually speaks are vendored; the434embedded set is the vocabulary, and `Vocabulary()` lists it.435436### 5.4 `lenses` — a picture attaches to a ground437438A world is a ground; a lens is a picture of it. The two are separable, and that439separation is what makes this an enricher: a ground published years ago can be440given another picture — a second map style, a later capture, somebody else's441raster warped into this world's space — without the ground changing and without442anything published being touched.443444This enricher does not derive pyramids. Deriving rasters is the generate lane's445work, and the derivation stamp a pyramid carries is written there. What arrives446here through `Context.Lenses` is the **offer**: these pictures were derived for447this ground, each with the stamp that says what it was made from. The enricher448attaches what the world does not already carry, updates in place what was449re-derived under a new stamp, and says nothing about a picture that has not450moved. `atlas enrich -lenses FILE` supplies the offers:451452```jsonc453{ "offers": { "night-city": [ { "name": "IGN Wiki", "tileSet": "aligned-night-city",454 "stamp": "…", "alignedWith": "cbp" } ] } }455```456457Aligned pyramids that the tile set already files under a world's own tile set458are attached by composition itself; the offer file is for pictures nothing has459claimed.460461---462463## 6. The feature-maturity score464465The score replaces bundle maturity. It is **unbounded, additive, and monotone**:466every feature earns points for each quality it verifiably has, and those sums467roll up feature → collection → world → volume.468469**No denominators, no ceilings.** A share moves when its denominator moves, so a470build that added five hundred features and described half of them could read as471a regression. Percentages survive only as diagnostics, and where a diagnostic472prints a share it divides by every feature rather than by the point features473alone, so a share cannot exceed its whole.474475The table is versioned data (`internal/enrich/maturity/points.json`, v1). A476re-weighting is a **new version**, not a mass failure.477478### 6.1 Point table v1479480| earner | quality | points |481| --- | --- | --- |482| feature | a name | 1 |483| feature | prose at all | 1 |484| feature | prose longer than 140 characters | +1 |485| feature | true coordinates, both axes | 1 |486| feature | each membership attribute (`atlas.hydro.huc12`, and the joins after it) | 1 |487| feature | its collection resolves artwork | 1 |488| feature | geometry: `floor(log2(1 + vertices))` | ×1 |489| feature | each other reading that pinned this place, from the merge ledger | 1 |490| collection | each registered semantic-conventions key it declares | 1 |491| world | each registered world-level key it declares | 1 |492| world | each lens | 1 |493| world | each zoom level a lens holds | 1 |494495Geometry is log-scaled because ground that is actually drawn is worth more than496ground that is merely claimed, while one over-noded polygon must not outweigh a497city. Corroboration is scored because two sources agreeing is evidence, and498evidence is quality. A private attribute earns nothing: the score measures499adoption of the *shared* vocabulary.500501### 6.2 The monotonicity gate502503An enrichment build whose score declines fails.504505- Two builds are compared **only under the same table version**. Across versions506 nothing is concluded.507- A decline is permitted up to what the later build's ledger accounts for in508 `corrections`. That is the one shape a decrease may take, and it has to be509 written down. The gate exists to reward good data and never to punish the510 removal of data that was wrong.511512### 6.3 The five axes, as diagnostics513514Annotation, cartography, structure, icons and conventions are the score's515breakdown. They are absolute measurements of516one build, never ranks within a library, and nothing gates on them. `atlas517measure` prints the score first and the axes beneath it, with every merge ledger518the payloads carry.519520---521522## 7. Curation523524`internal/enrich/curation/curation.json`, embedded, so a build carries its own525curation and an enriched volume cannot depend on what was on the operator's526disk. Every section carries its own prose as a sibling key.527528```jsonc529{530 "schema": 1,531 "queue": { "order": ["merge", "national", "stdicons", "lenses"] },532 "merge": {533 "matchRadiusPx": 160, "separateRadiusPx": 320, "nearbyFloorPx": 48,534 "attributes": { "donorFillsEmpty": [535 "atlas.note.text", "atlas.geo.lat", "atlas.geo.lon", "atlas.icon.std" ] }536 },537 "national": { "evidenceCollections": { "bend-or": ["Watersheds", "Subwatersheds",538 "Streams", "Waterbodies"] } }539}540```541542The reader refuses a separate radius that is not larger than the match radius —543nothing would ever be held back — and an attribute policy for a key the544conventions registry does not know. The one deliberate exception is545`atlas.note.text`, which is registered nowhere because no payload carries it: it546is the name a description travels under so that "which description wins" is547decided and recorded in the same vocabulary as everything else.548549---550551## 8. The CLI552553```554atlas enrich -archive DIR -tiles INDEX [-bundles DIR] [-evidence DIR]555 [-lenses FILE] [-n] [volume...]556atlas measure [-bundles DIR] [-json] [volume...]557```558559`enrich` groups every reading in the archive by the volume it is a reading of,560picks the newest capture to serve, folds the rest in, and composes. A volume the561queue had nothing to say about is not rebuilt and says so at `info` — the line562reads `nothing to add`, and the run ends `unchanged=1` rather than writing a563build. That is law 5 as an observable, and it is what makes running this over a564whole archive cheap. `-evidence DIR` resolves as `<dir>/<volume>/<name>`.565566`-archive` and `-tiles` are the generate lane's inputs and are staged in the567working copy; [generate.md §7.1](generate.md#71-where-the-inputs-are) says568where. A single-source volume with no donor — `tunic` — is the shortest way to569see law 5; `cyberpunk-2077`, whose archive holds two readings, is the shortest570way to see a merge.571572`measure` scores every build in a registry, newest-per-volume marked as serving.573574---575576## 9. What is proven577578How the lane is judged overall is [testing.md](testing.md); every suite below579runs under `make test`, over stated bundles and the committed corpus.580581| claim | held by |582| --- | --- |583| the merge decides by name and distance, a place is matched once, and a refused alignment is a refused merge | `internal/enrich/enrichers/merge`, with the fit itself in `internal/enrich/align` |584| the membership join claims only what every sampled position agrees on, and the survey makes no claims about itself | `internal/enrich/enrichers/national` |585| a standard glyph resolves only into an empty slot, once, under a provenance-spelling name, and an unanswerable declaration fails the build | `internal/enrich/enrichers/stdicons` |586| a lens offer attaches what a world lacks and repoints nothing | `internal/enrich/enrichers/lenses` |587| the score is unbounded, additive and monotone; shares cannot exceed their whole; the gate permits a decline only up to accounted corrections | `internal/enrich/maturity` |588| contributions apply additively, refuse overwrites, and round-trip their canonical form; the gate audits every account both ways | `internal/enrich` |589| the queue curation and the enrichers the binary offers are the same set | `internal/enrich` (`TestQueueHoldsCurationAndTheBinaryToEachOther`) |590| the seam is a copy, not a decision | `cmd/atlas` (`TestAdaptationRoundTrips`): a document adapted and adapted back is byte-identical |591592The deepest composed output of this lane in the tree is the corpus city:593`testdata/corpus/bundles/bend-or` carries the national membership claims and a594resolved standard glyph, and the format, island and browser suites all read it.595596An enriched build's manifest revision — and therefore its stamp and file597name — differs by construction from a plain build of the same captures; §2 is598the mechanism, and599[decision 18](decisions/0018-divergences-from-the-reference.md) records it600among the accepted divergences from the archived reference.